1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * 
  4  * Zimbra Collaboration Suite Web Client
  5  * Copyright (C) 2012 VMware, Inc.
  6  * 
  7  * The contents of this file are subject to the Zimbra Public License
  8  * Version 1.3 ("License"); you may not use this file except in
  9  * compliance with the License.  You may obtain a copy of the License at
 10  * http://www.zimbra.com/license.
 11  * 
 12  * Software distributed under the License is distributed on an "AS IS"
 13  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 14  * 
 15  * ***** END LICENSE BLOCK *****
 16  */
 17 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 18 (function(win) {
 19 	var whiteSpaceRe = /^\s*|\s*$/g,
 20 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 21 
 22 	var tinymce = {
 23 		majorVersion : '3',
 24 
 25 		minorVersion : '5.4.1',
 26 
 27 		releaseDate : '2012-06-24',
 28 
 29 		_init : function() {
 30 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 31 
 32 			t.isOpera = win.opera && opera.buildNumber;
 33 
 34 			t.isWebKit = /WebKit/.test(ua);
 35 
 36 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 37 
 38 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 39 
 40 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 41 
 42 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 43 
 44 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 45 
 46 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 47 
 48 			t.isMac = ua.indexOf('Mac') != -1;
 49 
 50 			t.isAir = /adobeair/i.test(ua);
 51 
 52 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 53 			
 54 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 55 
 56 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 57 			if (win.tinyMCEPreInit) {
 58 				t.suffix = tinyMCEPreInit.suffix;
 59 				t.baseURL = tinyMCEPreInit.base;
 60 				t.query = tinyMCEPreInit.query;
 61 				return;
 62 			}
 63 
 64 			// Get suffix and base
 65 			t.suffix = '';
 66 
 67 			// If base element found, add that infront of baseURL
 68 			nl = d.getElementsByTagName('base');
 69 			for (i=0; i<nl.length; i++) {
 70 				v = nl[i].href;
 71 				if (v) {
 72 					// Host only value like http://site.com or http://site.com:8008
 73 					if (/^https?:\/\/[^\/]+$/.test(v))
 74 						v += '/';
 75 
 76 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 77 				}
 78 			}
 79 
 80 			function getBase(n) {
 81 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 82 					if (/_(src|dev)\.js/g.test(n.src))
 83 						t.suffix = '_src';
 84 
 85 					if ((p = n.src.indexOf('?')) != -1)
 86 						t.query = n.src.substring(p + 1);
 87 
 88 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 89 
 90 					// If path to script is relative and a base href was found add that one infront
 91 					// the src property will always be an absolute one on non IE browsers and IE 8
 92 					// so this logic will basically only be executed on older IE versions
 93 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 94 						t.baseURL = base + t.baseURL;
 95 
 96 					return t.baseURL;
 97 				}
 98 
 99 				return null;
100 			};
101 
102 			// Check document
103 			nl = d.getElementsByTagName('script');
104 			for (i=0; i<nl.length; i++) {
105 				if (getBase(nl[i]))
106 					return;
107 			}
108 
109 			// Check head
110 			n = d.getElementsByTagName('head')[0];
111 			if (n) {
112 				nl = n.getElementsByTagName('script');
113 				for (i=0; i<nl.length; i++) {
114 					if (getBase(nl[i]))
115 						return;
116 				}
117 			}
118 
119 			return;
120 		},
121 
122 		is : function(o, t) {
123 			if (!t)
124 				return o !== undef;
125 
126 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
127 				return true;
128 
129 			return typeof(o) == t;
130 		},
131 
132 		makeMap : function(items, delim, map) {
133 			var i;
134 
135 			items = items || [];
136 			delim = delim || ',';
137 
138 			if (typeof(items) == "string")
139 				items = items.split(delim);
140 
141 			map = map || {};
142 
143 			i = items.length;
144 			while (i--)
145 				map[items[i]] = {};
146 
147 			return map;
148 		},
149 
150 		each : function(o, cb, s) {
151 			var n, l;
152 
153 			if (!o)
154 				return 0;
155 
156 			s = s || o;
157 
158 			if (o.length !== undef) {
159 				// Indexed arrays, needed for Safari
160 				for (n=0, l = o.length; n < l; n++) {
161 					if (cb.call(s, o[n], n, o) === false)
162 						return 0;
163 				}
164 			} else {
165 				// Hashtables
166 				for (n in o) {
167 					if (o.hasOwnProperty(n)) {
168 						if (cb.call(s, o[n], n, o) === false)
169 							return 0;
170 					}
171 				}
172 			}
173 
174 			return 1;
175 		},
176 
177 
178 		map : function(a, f) {
179 			var o = [];
180 
181 			tinymce.each(a, function(v) {
182 				o.push(f(v));
183 			});
184 
185 			return o;
186 		},
187 
188 		grep : function(a, f) {
189 			var o = [];
190 
191 			tinymce.each(a, function(v) {
192 				if (!f || f(v))
193 					o.push(v);
194 			});
195 
196 			return o;
197 		},
198 
199 		inArray : function(a, v) {
200 			var i, l;
201 
202 			if (a) {
203 				for (i = 0, l = a.length; i < l; i++) {
204 					if (a[i] === v)
205 						return i;
206 				}
207 			}
208 
209 			return -1;
210 		},
211 
212 		extend : function(obj, ext) {
213 			var i, l, name, args = arguments, value;
214 
215 			for (i = 1, l = args.length; i < l; i++) {
216 				ext = args[i];
217 				for (name in ext) {
218 					if (ext.hasOwnProperty(name)) {
219 						value = ext[name];
220 
221 						if (value !== undef) {
222 							obj[name] = value;
223 						}
224 					}
225 				}
226 			}
227 
228 			return obj;
229 		},
230 
231 
232 		trim : function(s) {
233 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
234 		},
235 
236 		create : function(s, p, root) {
237 			var t = this, sp, ns, cn, scn, c, de = 0;
238 
239 			// Parse : <prefix> <class>:<super class>
240 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
241 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
242 
243 			// Create namespace for new class
244 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
245 
246 			// Class already exists
247 			if (ns[cn])
248 				return;
249 
250 			// Make pure static class
251 			if (s[2] == 'static') {
252 				ns[cn] = p;
253 
254 				if (this.onCreate)
255 					this.onCreate(s[2], s[3], ns[cn]);
256 
257 				return;
258 			}
259 
260 			// Create default constructor
261 			if (!p[cn]) {
262 				p[cn] = function() {};
263 				de = 1;
264 			}
265 
266 			// Add constructor and methods
267 			ns[cn] = p[cn];
268 			t.extend(ns[cn].prototype, p);
269 
270 			// Extend
271 			if (s[5]) {
272 				sp = t.resolve(s[5]).prototype;
273 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
274 
275 				// Extend constructor
276 				c = ns[cn];
277 				if (de) {
278 					// Add passthrough constructor
279 					ns[cn] = function() {
280 						return sp[scn].apply(this, arguments);
281 					};
282 				} else {
283 					// Add inherit constructor
284 					ns[cn] = function() {
285 						this.parent = sp[scn];
286 						return c.apply(this, arguments);
287 					};
288 				}
289 				ns[cn].prototype[cn] = ns[cn];
290 
291 				// Add super methods
292 				t.each(sp, function(f, n) {
293 					ns[cn].prototype[n] = sp[n];
294 				});
295 
296 				// Add overridden methods
297 				t.each(p, function(f, n) {
298 					// Extend methods if needed
299 					if (sp[n]) {
300 						ns[cn].prototype[n] = function() {
301 							this.parent = sp[n];
302 							return f.apply(this, arguments);
303 						};
304 					} else {
305 						if (n != cn)
306 							ns[cn].prototype[n] = f;
307 					}
308 				});
309 			}
310 
311 			// Add static methods
312 			t.each(p['static'], function(f, n) {
313 				ns[cn][n] = f;
314 			});
315 
316 			if (this.onCreate)
317 				this.onCreate(s[2], s[3], ns[cn].prototype);
318 		},
319 
320 		walk : function(o, f, n, s) {
321 			s = s || this;
322 
323 			if (o) {
324 				if (n)
325 					o = o[n];
326 
327 				tinymce.each(o, function(o, i) {
328 					if (f.call(s, o, i, n) === false)
329 						return false;
330 
331 					tinymce.walk(o, f, n, s);
332 				});
333 			}
334 		},
335 
336 		createNS : function(n, o) {
337 			var i, v;
338 
339 			o = o || win;
340 
341 			n = n.split('.');
342 			for (i=0; i<n.length; i++) {
343 				v = n[i];
344 
345 				if (!o[v])
346 					o[v] = {};
347 
348 				o = o[v];
349 			}
350 
351 			return o;
352 		},
353 
354 		resolve : function(n, o) {
355 			var i, l;
356 
357 			o = o || win;
358 
359 			n = n.split('.');
360 			for (i = 0, l = n.length; i < l; i++) {
361 				o = o[n[i]];
362 
363 				if (!o)
364 					break;
365 			}
366 
367 			return o;
368 		},
369 
370 		addUnload : function(f, s) {
371 			var t = this, unload;
372 
373 			unload = function() {
374 				var li = t.unloads, o, n;
375 
376 				if (li) {
377 					// Call unload handlers
378 					for (n in li) {
379 						o = li[n];
380 
381 						if (o && o.func)
382 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
383 					}
384 
385 					// Detach unload function
386 					if (win.detachEvent) {
387 						win.detachEvent('onbeforeunload', fakeUnload);
388 						win.detachEvent('onunload', unload);
389 					} else if (win.removeEventListener)
390 						win.removeEventListener('unload', unload, false);
391 
392 					// Destroy references
393 					t.unloads = o = li = w = unload = 0;
394 
395 					// Run garbarge collector on IE
396 					if (win.CollectGarbage)
397 						CollectGarbage();
398 				}
399 			};
400 
401 			function fakeUnload() {
402 				var d = document;
403 
404 				function stop() {
405 					// Prevent memory leak
406 					d.detachEvent('onstop', stop);
407 
408 					// Call unload handler
409 					if (unload)
410 						unload();
411 
412 					d = 0;
413 				};
414 
415 				// Is there things still loading, then do some magic
416 				if (d.readyState == 'interactive') {
417 					// Fire unload when the currently loading page is stopped
418 					if (d)
419 						d.attachEvent('onstop', stop);
420 
421 					// Remove onstop listener after a while to prevent the unload function
422 					// to execute if the user presses cancel in an onbeforeunload
423 					// confirm dialog and then presses the browser stop button
424 					win.setTimeout(function() {
425 						if (d)
426 							d.detachEvent('onstop', stop);
427 					}, 0);
428 				}
429 			};
430 
431 			f = {func : f, scope : s || this};
432 
433 			if (!t.unloads) {
434 				// Attach unload handler
435 				if (win.attachEvent) {
436 					win.attachEvent('onunload', unload);
437 					win.attachEvent('onbeforeunload', fakeUnload);
438 				} else if (win.addEventListener)
439 					win.addEventListener('unload', unload, false);
440 
441 				// Setup initial unload handler array
442 				t.unloads = [f];
443 			} else
444 				t.unloads.push(f);
445 
446 			return f;
447 		},
448 
449 		removeUnload : function(f) {
450 			var u = this.unloads, r = null;
451 
452 			tinymce.each(u, function(o, i) {
453 				if (o && o.func == f) {
454 					u.splice(i, 1);
455 					r = f;
456 					return false;
457 				}
458 			});
459 
460 			return r;
461 		},
462 
463 		explode : function(s, d) {
464 			if (!s || tinymce.is(s, 'array')) {
465 				return s;
466 			}
467 
468 			return tinymce.map(s.split(d || ','), tinymce.trim);
469 		},
470 
471 		_addVer : function(u) {
472 			var v;
473 
474 			if (!this.query)
475 				return u;
476 
477 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
478 
479 			if (u.indexOf('#') == -1)
480 				return u + v;
481 
482 			return u.replace('#', v + '#');
483 		},
484 
485 		// Fix function for IE 9 where regexps isn't working correctly
486 		// Todo: remove me once MS fixes the bug
487 		_replace : function(find, replace, str) {
488 			// On IE9 we have to fake $x replacement
489 			if (isRegExpBroken) {
490 				return str.replace(find, function() {
491 					var val = replace, args = arguments, i;
492 
493 					for (i = 0; i < args.length - 2; i++) {
494 						if (args[i] === undef) {
495 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
496 						} else {
497 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
498 						}
499 					}
500 
501 					return val;
502 				});
503 			}
504 
505 			return str.replace(find, replace);
506 		}
507 
508 		};
509 
510 	// Initialize the API
511 	tinymce._init();
512 
513 	// Expose tinymce namespace to the global namespace (window)
514 	win.tinymce = win.tinyMCE = tinymce;
515 
516 	// Describe the different namespaces
517 
518 	})(window);
519 
520 
521 
522 (function() {
523 	if (!window.Prototype)
524 		return alert("Load prototype first!");
525 
526 	// Patch in core NS functions
527 	tinymce.extend(tinymce, {
528 		trim : function(s) {return s ? s.strip() : '';},
529 		inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
530 	});
531 
532 	// Patch in functions in various clases
533 	// Add a "#ifndefjquery" statement around each core API function you add below
534 	var patches = {
535 		'tinymce.util.JSON' : {
536 			/*serialize : function(o) {
537 				return o.toJSON();
538 			}*/
539 		}
540 	};
541 
542 	// Patch functions after a class is created
543 	tinymce.onCreate = function(ty, c, p) {
544 		tinymce.extend(p, patches[c]);
545 	};
546 })();
547 
548 
549 tinymce.create('tinymce.util.Dispatcher', {
550 	scope : null,
551 	listeners : null,
552 	inDispatch: false,
553 
554 	Dispatcher : function(scope) {
555 		this.scope = scope || this;
556 		this.listeners = [];
557 	},
558 
559 	add : function(callback, scope) {
560 		this.listeners.push({cb : callback, scope : scope || this.scope});
561 
562 		return callback;
563 	},
564 
565 	addToTop : function(callback, scope) {
566 		var self = this, listener = {cb : callback, scope : scope || self.scope};
567 
568 		// Create new listeners if addToTop is executed in a dispatch loop
569 		if (self.inDispatch) {
570 			self.listeners = [listener].concat(self.listeners);
571 		} else {
572 			self.listeners.unshift(listener);
573 		}
574 
575 		return callback;
576 	},
577 
578 	remove : function(callback) {
579 		var listeners = this.listeners, output = null;
580 
581 		tinymce.each(listeners, function(listener, i) {
582 			if (callback == listener.cb) {
583 				output = listener;
584 				listeners.splice(i, 1);
585 				return false;
586 			}
587 		});
588 
589 		return output;
590 	},
591 
592 	dispatch : function() {
593 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
594 
595 		self.inDispatch = true;
596 		
597 		// Needs to be a real loop since the listener count might change while looping
598 		// And this is also more efficient
599 		for (i = 0; i < listeners.length; i++) {
600 			listener = listeners[i];
601 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
602 
603 			if (returnValue === false)
604 				break;
605 		}
606 
607 		self.inDispatch = false;
608 
609 		return returnValue;
610 	}
611 
612 	});
613 
614 (function() {
615 	var each = tinymce.each;
616 
617 	tinymce.create('tinymce.util.URI', {
618 		URI : function(u, s) {
619 			var t = this, o, a, b, base_url;
620 
621 			// Trim whitespace
622 			u = tinymce.trim(u);
623 
624 			// Default settings
625 			s = t.settings = s || {};
626 
627 			// Strange app protocol that isn't http/https or local anchor
628 			// For example: mailto,skype,tel etc.
629 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
630 				t.source = u;
631 				return;
632 			}
633 
634 			// Absolute path with no host, fake host and protocol
635 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
636 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
637 
638 			// Relative path http:// or protocol relative //path
639 			if (!/^[\w\-]*:?\/\//.test(u)) {
640 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
641 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
642 			}
643 
644 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
645 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
646 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
647 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
648 				var s = u[i];
649 
650 				// Zope 3 workaround, they use @@something
651 				if (s)
652 					s = s.replace(/\(mce_at\)/g, '@@');
653 
654 				t[v] = s;
655 			});
656 
657 			b = s.base_uri;
658 			if (b) {
659 				if (!t.protocol)
660 					t.protocol = b.protocol;
661 
662 				if (!t.userInfo)
663 					t.userInfo = b.userInfo;
664 
665 				if (!t.port && t.host === 'mce_host')
666 					t.port = b.port;
667 
668 				if (!t.host || t.host === 'mce_host')
669 					t.host = b.host;
670 
671 				t.source = '';
672 			}
673 
674 			//t.path = t.path || '/';
675 		},
676 
677 		setPath : function(p) {
678 			var t = this;
679 
680 			p = /^(.*?)\/?(\w+)?$/.exec(p);
681 
682 			// Update path parts
683 			t.path = p[0];
684 			t.directory = p[1];
685 			t.file = p[2];
686 
687 			// Rebuild source
688 			t.source = '';
689 			t.getURI();
690 		},
691 
692 		toRelative : function(u) {
693 			var t = this, o;
694 
695 			if (u === "./")
696 				return u;
697 
698 			u = new tinymce.util.URI(u, {base_uri : t});
699 
700 			// Not on same domain/port or protocol
701 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
702 				return u.getURI();
703 
704 			var tu = t.getURI(), uu = u.getURI();
705 			
706 			// Allow usage of the base_uri when relative_urls = true
707 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
708 				return tu;
709 
710 			o = t.toRelPath(t.path, u.path);
711 
712 			// Add query
713 			if (u.query)
714 				o += '?' + u.query;
715 
716 			// Add anchor
717 			if (u.anchor)
718 				o += '#' + u.anchor;
719 
720 			return o;
721 		},
722 	
723 		toAbsolute : function(u, nh) {
724 			u = new tinymce.util.URI(u, {base_uri : this});
725 
726 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
727 		},
728 
729 		toRelPath : function(base, path) {
730 			var items, bp = 0, out = '', i, l;
731 
732 			// Split the paths
733 			base = base.substring(0, base.lastIndexOf('/'));
734 			base = base.split('/');
735 			items = path.split('/');
736 
737 			if (base.length >= items.length) {
738 				for (i = 0, l = base.length; i < l; i++) {
739 					if (i >= items.length || base[i] != items[i]) {
740 						bp = i + 1;
741 						break;
742 					}
743 				}
744 			}
745 
746 			if (base.length < items.length) {
747 				for (i = 0, l = items.length; i < l; i++) {
748 					if (i >= base.length || base[i] != items[i]) {
749 						bp = i + 1;
750 						break;
751 					}
752 				}
753 			}
754 
755 			if (bp === 1)
756 				return path;
757 
758 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
759 				out += "../";
760 
761 			for (i = bp - 1, l = items.length; i < l; i++) {
762 				if (i != bp - 1)
763 					out += "/" + items[i];
764 				else
765 					out += items[i];
766 			}
767 
768 			return out;
769 		},
770 
771 		toAbsPath : function(base, path) {
772 			var i, nb = 0, o = [], tr, outPath;
773 
774 			// Split paths
775 			tr = /\/$/.test(path) ? '/' : '';
776 			base = base.split('/');
777 			path = path.split('/');
778 
779 			// Remove empty chunks
780 			each(base, function(k) {
781 				if (k)
782 					o.push(k);
783 			});
784 
785 			base = o;
786 
787 			// Merge relURLParts chunks
788 			for (i = path.length - 1, o = []; i >= 0; i--) {
789 				// Ignore empty or .
790 				if (path[i].length === 0 || path[i] === ".")
791 					continue;
792 
793 				// Is parent
794 				if (path[i] === '..') {
795 					nb++;
796 					continue;
797 				}
798 
799 				// Move up
800 				if (nb > 0) {
801 					nb--;
802 					continue;
803 				}
804 
805 				o.push(path[i]);
806 			}
807 
808 			i = base.length - nb;
809 
810 			// If /a/b/c or /
811 			if (i <= 0)
812 				outPath = o.reverse().join('/');
813 			else
814 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
815 
816 			// Add front / if it's needed
817 			if (outPath.indexOf('/') !== 0)
818 				outPath = '/' + outPath;
819 
820 			// Add traling / if it's needed
821 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
822 				outPath += tr;
823 
824 			return outPath;
825 		},
826 
827 		getURI : function(nh) {
828 			var s, t = this;
829 
830 			// Rebuild source
831 			if (!t.source || nh) {
832 				s = '';
833 
834 				if (!nh) {
835 					if (t.protocol)
836 						s += t.protocol + '://';
837 
838 					if (t.userInfo)
839 						s += t.userInfo + '@';
840 
841 					if (t.host)
842 						s += t.host;
843 
844 					if (t.port)
845 						s += ':' + t.port;
846 				}
847 
848 				if (t.path)
849 					s += t.path;
850 
851 				if (t.query)
852 					s += '?' + t.query;
853 
854 				if (t.anchor)
855 					s += '#' + t.anchor;
856 
857 				t.source = s;
858 			}
859 
860 			return t.source;
861 		}
862 	});
863 })();
864 
865 (function() {
866 	var each = tinymce.each;
867 
868 	tinymce.create('static tinymce.util.Cookie', {
869 		getHash : function(n) {
870 			var v = this.get(n), h;
871 
872 			if (v) {
873 				each(v.split('&'), function(v) {
874 					v = v.split('=');
875 					h = h || {};
876 					h[unescape(v[0])] = unescape(v[1]);
877 				});
878 			}
879 
880 			return h;
881 		},
882 
883 		setHash : function(n, v, e, p, d, s) {
884 			var o = '';
885 
886 			each(v, function(v, k) {
887 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
888 			});
889 
890 			this.set(n, o, e, p, d, s);
891 		},
892 
893 		get : function(n) {
894 			var c = document.cookie, e, p = n + "=", b;
895 
896 			// Strict mode
897 			if (!c)
898 				return;
899 
900 			b = c.indexOf("; " + p);
901 
902 			if (b == -1) {
903 				b = c.indexOf(p);
904 
905 				if (b !== 0)
906 					return null;
907 			} else
908 				b += 2;
909 
910 			e = c.indexOf(";", b);
911 
912 			if (e == -1)
913 				e = c.length;
914 
915 			return unescape(c.substring(b + p.length, e));
916 		},
917 
918 		set : function(n, v, e, p, d, s) {
919 			document.cookie = n + "=" + escape(v) +
920 				((e) ? "; expires=" + e.toGMTString() : "") +
921 				((p) ? "; path=" + escape(p) : "") +
922 				((d) ? "; domain=" + d : "") +
923 				((s) ? "; secure" : "");
924 		},
925 
926 		remove : function(name, path, domain) {
927 			var date = new Date();
928 
929 			date.setTime(date.getTime() - 1000);
930 
931 			this.set(name, '', date, path, domain);
932 		}
933 	});
934 })();
935 
936 (function() {
937 	function serialize(o, quote) {
938 		var i, v, t, name;
939 
940 		quote = quote || '"';
941 
942 		if (o == null)
943 			return 'null';
944 
945 		t = typeof o;
946 
947 		if (t == 'string') {
948 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
949 
950 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
951 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
952 				if (quote === '"' && a === "'")
953 					return a;
954 
955 				i = v.indexOf(b);
956 
957 				if (i + 1)
958 					return '\\' + v.charAt(i + 1);
959 
960 				a = b.charCodeAt().toString(16);
961 
962 				return '\\u' + '0000'.substring(a.length) + a;
963 			}) + quote;
964 		}
965 
966 		if (t == 'object') {
967 			if (o.hasOwnProperty && o instanceof Array) {
968 					for (i=0, v = '['; i<o.length; i++)
969 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
970 
971 					return v + ']';
972 				}
973 
974 				v = '{';
975 
976 				for (name in o) {
977 					if (o.hasOwnProperty(name)) {
978 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
979 					}
980 				}
981 
982 				return v + '}';
983 		}
984 
985 		return '' + o;
986 	};
987 
988 	tinymce.util.JSON = {
989 		serialize: serialize,
990 
991 		parse: function(s) {
992 			try {
993 				return eval('(' + s + ')');
994 			} catch (ex) {
995 				// Ignore
996 			}
997 		}
998 
999 		};
1000 })();
1001 
1002 tinymce.create('static tinymce.util.XHR', {
1003 	send : function(o) {
1004 		var x, t, w = window, c = 0;
1005 
1006 		function ready() {
1007 			if (!o.async || x.readyState == 4 || c++ > 10000) {
1008 				if (o.success && c < 10000 && x.status == 200)
1009 					o.success.call(o.success_scope, '' + x.responseText, x, o);
1010 				else if (o.error)
1011 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1012 
1013 				x = null;
1014 			} else
1015 				w.setTimeout(ready, 10);
1016 		};
1017 
1018 		// Default settings
1019 		o.scope = o.scope || this;
1020 		o.success_scope = o.success_scope || o.scope;
1021 		o.error_scope = o.error_scope || o.scope;
1022 		o.async = o.async === false ? false : true;
1023 		o.data = o.data || '';
1024 
1025 		function get(s) {
1026 			x = 0;
1027 
1028 			try {
1029 				x = new ActiveXObject(s);
1030 			} catch (ex) {
1031 			}
1032 
1033 			return x;
1034 		};
1035 
1036 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1037 
1038 		if (x) {
1039 			if (x.overrideMimeType)
1040 				x.overrideMimeType(o.content_type);
1041 
1042 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1043 
1044 			if (o.content_type)
1045 				x.setRequestHeader('Content-Type', o.content_type);
1046 
1047 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1048 
1049 			x.send(o.data);
1050 
1051 			// Syncronous request
1052 			if (!o.async)
1053 				return ready();
1054 
1055 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1056 			t = w.setTimeout(ready, 10);
1057 		}
1058 	}
1059 });
1060 
1061 (function() {
1062 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1063 
1064 	tinymce.create('tinymce.util.JSONRequest', {
1065 		JSONRequest : function(s) {
1066 			this.settings = extend({
1067 			}, s);
1068 			this.count = 0;
1069 		},
1070 
1071 		send : function(o) {
1072 			var ecb = o.error, scb = o.success;
1073 
1074 			o = extend(this.settings, o);
1075 
1076 			o.success = function(c, x) {
1077 				c = JSON.parse(c);
1078 
1079 				if (typeof(c) == 'undefined') {
1080 					c = {
1081 						error : 'JSON Parse error.'
1082 					};
1083 				}
1084 
1085 				if (c.error)
1086 					ecb.call(o.error_scope || o.scope, c.error, x);
1087 				else
1088 					scb.call(o.success_scope || o.scope, c.result);
1089 			};
1090 
1091 			o.error = function(ty, x) {
1092 				if (ecb)
1093 					ecb.call(o.error_scope || o.scope, ty, x);
1094 			};
1095 
1096 			o.data = JSON.serialize({
1097 				id : o.id || 'c' + (this.count++),
1098 				method : o.method,
1099 				params : o.params
1100 			});
1101 
1102 			// JSON content type for Ruby on rails. Bug: #1883287
1103 			o.content_type = 'application/json';
1104 
1105 			XHR.send(o);
1106 		},
1107 
1108 		'static' : {
1109 			sendRPC : function(o) {
1110 				return new tinymce.util.JSONRequest().send(o);
1111 			}
1112 		}
1113 	});
1114 }());
1115 (function(tinymce){
1116 	tinymce.VK = {
1117 		BACKSPACE: 8,
1118 		DELETE: 46,
1119 		DOWN: 40,
1120 		ENTER: 13,
1121 		LEFT: 37,
1122 		RIGHT: 39,
1123 		SPACEBAR: 32,
1124 		TAB: 9,
1125 		UP: 38,
1126 
1127 		modifierPressed: function (e) {
1128 			return e.shiftKey || e.ctrlKey || e.altKey;
1129 		},
1130 
1131 		metaKeyPressed: function(e) {
1132 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1133 		}
1134 	};
1135 })(tinymce);
1136 
1137 tinymce.util.Quirks = function(editor) {
1138 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1139 
1140 	function setEditorCommandState(cmd, state) {
1141 		try {
1142 			editor.getDoc().execCommand(cmd, false, state);
1143 		} catch (ex) {
1144 			// Ignore
1145 		}
1146 	}
1147 
1148 	function getDocumentMode() {
1149 		var documentMode = editor.getDoc().documentMode;
1150 
1151 		return documentMode ? documentMode : 6;
1152 	};
1153 
1154 	function cleanupStylesWhenDeleting() {
1155 		function removeMergedFormatSpans(isDelete) {
1156 			var rng, blockElm, node, clonedSpan;
1157 
1158 			rng = selection.getRng();
1159 
1160 			// Find root block
1161 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1162 
1163 			// On delete clone the root span of the next block element
1164 			if (isDelete)
1165 				blockElm = dom.getNext(blockElm, dom.isBlock);
1166 
1167 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1168 			if (blockElm) {
1169 				node = blockElm.firstChild;
1170 
1171 				// Ignore empty text nodes
1172 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1173 					node = node.nextSibling;
1174 
1175 				if (node && node.nodeName === 'SPAN') {
1176 					clonedSpan = node.cloneNode(false);
1177 				}
1178 			}
1179 
1180 			// Do the backspace/delete action
1181 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1182 
1183 			// Find all odd apple-style-spans
1184 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1185 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1186 				var bm = selection.getBookmark();
1187 
1188 				if (clonedSpan) {
1189 					dom.replace(clonedSpan.cloneNode(false), span, true);
1190 				} else {
1191 					dom.remove(span, true);
1192 				}
1193 
1194 				// Restore the selection
1195 				selection.moveToBookmark(bm);
1196 			});
1197 		};
1198 
1199 		editor.onKeyDown.add(function(editor, e) {
1200 			var isDelete;
1201 
1202 			isDelete = e.keyCode == DELETE;
1203 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1204 				e.preventDefault();
1205 				removeMergedFormatSpans(isDelete);
1206 			}
1207 		});
1208 
1209 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1210 	};
1211 	
1212 	function emptyEditorWhenDeleting() {
1213 		function serializeRng(rng) {
1214 			var body = dom.create("body");
1215 			var contents = rng.cloneContents();
1216 			body.appendChild(contents);
1217 			return selection.serializer.serialize(body, {format: 'html'});
1218 		}
1219 
1220 		function allContentsSelected(rng) {
1221 			var selection = serializeRng(rng);
1222 
1223 			var allRng = dom.createRng();
1224 			allRng.selectNode(editor.getBody());
1225 
1226 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1227 			return selection === allSelection;
1228 		}
1229 
1230 		editor.onKeyDown.add(function(editor, e) {
1231 			var keyCode = e.keyCode, isCollapsed;
1232 
1233 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1234 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1235 				isCollapsed = editor.selection.isCollapsed();
1236 
1237 				// Selection is collapsed but the editor isn't empty
1238 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1239 					return;
1240 				}
1241 
1242 				// IE deletes all contents correctly when everything is selected
1243 				if (tinymce.isIE && !isCollapsed) {
1244 					return;
1245 				}
1246 
1247 				// Selection isn't collapsed but not all the contents is selected
1248 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1249 					return;
1250 				}
1251 
1252 				// Manually empty the editor
1253 				editor.setContent('');
1254 				editor.selection.setCursorLocation(editor.getBody(), 0);
1255 				editor.nodeChanged();
1256 			}
1257 		});
1258 	};
1259 
1260 	function selectAll() {
1261 		editor.onKeyDown.add(function(editor, e) {
1262 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1263 				e.preventDefault();
1264 				editor.execCommand('SelectAll');
1265 			}
1266 		});
1267 	};
1268 
1269 	function inputMethodFocus() {
1270 		if (!editor.settings.content_editable) {
1271 			// Case 1 IME doesn't initialize if you focus the document
1272 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1273 				selection.setRng(selection.getRng());
1274 			});
1275 
1276 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1277 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1278 				if (e.target == editor.getDoc().documentElement) {
1279 					editor.getWin().focus();
1280 					selection.setRng(selection.getRng());
1281 				}
1282 			});
1283 		}
1284 	};
1285 
1286 	function removeHrOnBackspace() {
1287 		editor.onKeyDown.add(function(editor, e) {
1288 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1289 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1290 					var node = selection.getNode();
1291 					var previousSibling = node.previousSibling;
1292 
1293 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1294 						dom.remove(previousSibling);
1295 						tinymce.dom.Event.cancel(e);
1296 					}
1297 				}
1298 			}
1299 		})
1300 	}
1301 
1302 	function focusBody() {
1303 		// Fix for a focus bug in FF 3.x where the body element
1304 		// wouldn't get proper focus if the user clicked on the HTML element
1305 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1306 			editor.onMouseDown.add(function(editor, e) {
1307 				if (e.target.nodeName === "HTML") {
1308 					var body = editor.getBody();
1309 
1310 					// Blur the body it's focused but not correctly focused
1311 					body.blur();
1312 
1313 					// Refocus the body after a little while
1314 					setTimeout(function() {
1315 						body.focus();
1316 					}, 0);
1317 				}
1318 			});
1319 		}
1320 	};
1321 
1322 	function selectControlElements() {
1323 		editor.onClick.add(function(editor, e) {
1324 			e = e.target;
1325 
1326 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1327 			// WebKit can't even do simple things like selecting an image
1328 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1329 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1330 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1331 			}
1332 
1333 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1334 				selection.select(e);
1335 			}
1336 
1337 			editor.nodeChanged();
1338 		});
1339 	};
1340 
1341 	function removeStylesWhenDeletingAccrossBlockElements() {
1342 		function getAttributeApplyFunction() {
1343 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1344 
1345 			return function() {
1346 				var target = selection.getStart();
1347 
1348 				if (target !== editor.getBody()) {
1349 					dom.setAttrib(target, "style", null);
1350 
1351 					tinymce.each(template, function(attr) {
1352 						target.setAttributeNode(attr.cloneNode(true));
1353 					});
1354 				}
1355 			};
1356 		}
1357 
1358 		function isSelectionAcrossElements() {
1359 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1360 		}
1361 
1362 		function blockEvent(editor, e) {
1363 			e.preventDefault();
1364 			return false;
1365 		}
1366 
1367 		editor.onKeyPress.add(function(editor, e) {
1368 			var applyAttributes;
1369 
1370 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1371 				applyAttributes = getAttributeApplyFunction();
1372 				editor.getDoc().execCommand('delete', false, null);
1373 				applyAttributes();
1374 				e.preventDefault();
1375 				return false;
1376 			}
1377 		});
1378 
1379 		dom.bind(editor.getDoc(), 'cut', function(e) {
1380 			var applyAttributes;
1381 
1382 			if (isSelectionAcrossElements()) {
1383 				applyAttributes = getAttributeApplyFunction();
1384 				editor.onKeyUp.addToTop(blockEvent);
1385 
1386 				setTimeout(function() {
1387 					applyAttributes();
1388 					editor.onKeyUp.remove(blockEvent);
1389 				}, 0);
1390 			}
1391 		});
1392 	}
1393 
1394 	function selectionChangeNodeChanged() {
1395 		var lastRng, selectionTimer;
1396 
1397 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1398 			if (selectionTimer) {
1399 				clearTimeout(selectionTimer);
1400 				selectionTimer = 0;
1401 			}
1402 
1403 			selectionTimer = window.setTimeout(function() {
1404 				var rng = selection.getRng();
1405 
1406 				// Compare the ranges to see if it was a real change or not
1407 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1408 					editor.nodeChanged();
1409 					lastRng = rng;
1410 				}
1411 			}, 50);
1412 		});
1413 	}
1414 
1415 	function ensureBodyHasRoleApplication() {
1416 		document.body.setAttribute("role", "application");
1417 	}
1418 
1419 	function disableBackspaceIntoATable() {
1420 		editor.onKeyDown.add(function(editor, e) {
1421 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1422 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1423 					var previousSibling = selection.getNode().previousSibling;
1424 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1425 						return tinymce.dom.Event.cancel(e);
1426 					}
1427 				}
1428 			}
1429 		})
1430 	}
1431 
1432 	function addNewLinesBeforeBrInPre() {
1433 		// IE8+ rendering mode does the right thing with BR in PRE
1434 		if (getDocumentMode() > 7) {
1435 			return;
1436 		}
1437 
1438 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1439 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1440 		setEditorCommandState('RespectVisibilityInDesign', true);
1441 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1442 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1443 
1444 		// Adds a \n before all BR elements in PRE to get them visual
1445 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1446 			var i = nodes.length, brNodes, j, brElm, sibling;
1447 
1448 			while (i--) {
1449 				brNodes = nodes[i].getAll('br');
1450 				j = brNodes.length;
1451 				while (j--) {
1452 					brElm = brNodes[j];
1453 
1454 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1455 					sibling = brElm.prev;
1456 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1457 						sibling.value += '\n';
1458 					} else {
1459 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1460 					}
1461 				}
1462 			}
1463 		});
1464 
1465 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1466 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1467 			var i = nodes.length, brNodes, j, brElm, sibling;
1468 
1469 			while (i--) {
1470 				brNodes = nodes[i].getAll('br');
1471 				j = brNodes.length;
1472 				while (j--) {
1473 					brElm = brNodes[j];
1474 					sibling = brElm.prev;
1475 					if (sibling && sibling.type == 3) {
1476 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1477 					}
1478 				}
1479 			}
1480 		});
1481 	}
1482 
1483 	function removePreSerializedStylesWhenSelectingControls() {
1484 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1485 			var value, node = selection.getNode();
1486 
1487 			// Moved styles to attributes on IMG eements
1488 			if (node.nodeName == 'IMG') {
1489 				// Convert style width to width attribute
1490 				if (value = dom.getStyle(node, 'width')) {
1491 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1492 					dom.setStyle(node, 'width', '');
1493 				}
1494 
1495 				// Convert style height to height attribute
1496 				if (value = dom.getStyle(node, 'height')) {
1497 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1498 					dom.setStyle(node, 'height', '');
1499 				}
1500 			}
1501 		});
1502 	}
1503 
1504 	function keepInlineElementOnDeleteBackspace() {
1505 		editor.onKeyDown.add(function(editor, e) {
1506 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1507 
1508 			isDelete = e.keyCode == DELETE;
1509 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1510 				rng = selection.getRng();
1511 				container = rng.startContainer;
1512 				offset = rng.startOffset;
1513 				collapsed = rng.collapsed;
1514 
1515 				// Override delete if the start container is a text node and is at the beginning of text or
1516 				// just before/after the last character to be deleted in collapsed mode
1517 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1518 					nonEmptyElements = editor.schema.getNonEmptyElements();
1519 
1520 					// Prevent default logic since it's broken
1521 					e.preventDefault();
1522 
1523 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1524 					brElm = dom.create('br', {id: '__tmp'});
1525 					container.parentNode.insertBefore(brElm, container);
1526 
1527 					// Do the browser delete
1528 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1529 
1530 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1531 					container = selection.getRng().startContainer;
1532 					sibling = container.previousSibling;
1533 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1534 						dom.remove(sibling);
1535 					}
1536 
1537 					// Remove the temp element we inserted
1538 					dom.remove('__tmp');
1539 				}
1540 			}
1541 		});
1542 	}
1543 
1544 	function removeBlockQuoteOnBackSpace() {
1545 		// Add block quote deletion handler
1546 		editor.onKeyDown.add(function(editor, e) {
1547 			var rng, container, offset, root, parent;
1548 
1549 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1550 				return;
1551 			}
1552 
1553 			rng = selection.getRng();
1554 			container = rng.startContainer;
1555 			offset = rng.startOffset;
1556 			root = dom.getRoot();
1557 			parent = container;
1558 
1559 			if (!rng.collapsed || offset !== 0) {
1560 				return;
1561 			}
1562 
1563 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1564 				parent = parent.parentNode;
1565 			}
1566 
1567 			// Is the cursor at the beginning of a blockquote?
1568 			if (parent.tagName === 'BLOCKQUOTE') {
1569 				// Remove the blockquote
1570 				editor.formatter.toggle('blockquote', null, parent);
1571 
1572 				// Move the caret to the beginning of container
1573 				rng.setStart(container, 0);
1574 				rng.setEnd(container, 0);
1575 				selection.setRng(rng);
1576 				selection.collapse(false);
1577 			}
1578 		});
1579 	};
1580 
1581 	function setGeckoEditingOptions() {
1582 		function setOpts() {
1583 			editor._refreshContentEditable();
1584 
1585 			setEditorCommandState("StyleWithCSS", false);
1586 			setEditorCommandState("enableInlineTableEditing", false);
1587 
1588 			if (!settings.object_resizing) {
1589 				setEditorCommandState("enableObjectResizing", false);
1590 			}
1591 		};
1592 
1593 		if (!settings.readonly) {
1594 			editor.onBeforeExecCommand.add(setOpts);
1595 			editor.onMouseDown.add(setOpts);
1596 		}
1597 	};
1598 
1599 	function addBrAfterLastLinks() {
1600 		function fixLinks(editor, o) {
1601 			tinymce.each(dom.select('a'), function(node) {
1602 				var parentNode = node.parentNode, root = dom.getRoot();
1603 
1604 				if (parentNode.lastChild === node) {
1605 					while (parentNode && !dom.isBlock(parentNode)) {
1606 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1607 							return;
1608 						}
1609 
1610 						parentNode = parentNode.parentNode;
1611 					}
1612 
1613 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1614 				}
1615 			});
1616 		};
1617 
1618 		editor.onExecCommand.add(function(editor, cmd) {
1619 			if (cmd === 'CreateLink') {
1620 				fixLinks(editor);
1621 			}
1622 		});
1623 
1624 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1625 	};
1626 
1627 	function setDefaultBlockType() {
1628 		if (settings.forced_root_block) {
1629 			editor.onInit.add(function() {
1630 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1631 			});
1632 		}
1633 	}
1634 
1635 	function removeGhostSelection() {
1636 		function repaint(sender, args) {
1637 			if (!sender || !args.initial) {
1638 				editor.execCommand('mceRepaint');
1639 			}
1640 		};
1641 
1642 		editor.onUndo.add(repaint);
1643 		editor.onRedo.add(repaint);
1644 		editor.onSetContent.add(repaint);
1645 	};
1646 
1647 	function deleteControlItemOnBackSpace() {
1648 		editor.onKeyDown.add(function(editor, e) {
1649 			var rng;
1650 
1651 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1652 				rng = editor.getDoc().selection.createRange();
1653 				if (rng && rng.item) {
1654 					e.preventDefault();
1655 					editor.undoManager.beforeChange();
1656 					dom.remove(rng.item(0));
1657 					editor.undoManager.add();
1658 				}
1659 			}
1660 		});
1661 	};
1662 
1663 	function renderEmptyBlocksFix() {
1664 		var emptyBlocksCSS;
1665 
1666 		// IE10+
1667 		if (getDocumentMode() >= 10) {
1668 			emptyBlocksCSS = '';
1669 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1670 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1671 			});
1672 
1673 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1674 		}
1675 	};
1676 
1677 	function fakeImageResize() {
1678 		var mouseDownImg, startX, startY, startW, startH;
1679 
1680 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1681 			return;
1682 		}
1683 
1684 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1685 
1686 		function resizeImage(e) {
1687 			var deltaX, deltaY, ratio, width, height;
1688 
1689 			if (mouseDownImg) {
1690 				deltaX = e.screenX - startX;
1691 				deltaY = e.screenY - startY;
1692 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1693 
1694 				// Only update styles if the user draged one pixel or more
1695 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1696 					// Constrain proportions
1697 					width = Math.round(startW * ratio);
1698 					height = Math.round(startH * ratio);
1699 
1700 					// Resize by using style or attribute
1701 					if (mouseDownImg.style.width) {
1702 						dom.setStyle(mouseDownImg, 'width', width);
1703 					} else {
1704 						dom.setAttrib(mouseDownImg, 'width', width);
1705 					}
1706 
1707 					// Resize by using style or attribute
1708 					if (mouseDownImg.style.height) {
1709 						dom.setStyle(mouseDownImg, 'height', height);
1710 					} else {
1711 						dom.setAttrib(mouseDownImg, 'height', height);
1712 					}
1713 
1714 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1715 						dom.addClass(editor.getBody(), 'mceResizeImages');
1716 					}
1717 				}
1718 			}
1719 		};
1720 
1721 		editor.onMouseDown.add(function(editor, e) {
1722 			var target = e.target;
1723 
1724 			if (target.nodeName == "IMG") {
1725 				mouseDownImg = target;
1726 				startX = e.screenX;
1727 				startY = e.screenY;
1728 				startW = mouseDownImg.clientWidth;
1729 				startH = mouseDownImg.clientHeight;
1730 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1731 				e.preventDefault();
1732 			}
1733 		});
1734 
1735 		// Unbind events on node change and restore resize cursor
1736 		editor.onNodeChange.add(function() {
1737 			if (mouseDownImg) {
1738 				mouseDownImg = null;
1739 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1740 			}
1741 
1742 			if (selection.getNode().nodeName == "IMG") {
1743 				dom.addClass(editor.getBody(), 'mceResizeImages');
1744 			} else {
1745 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1746 			}
1747 		});
1748 	};
1749 
1750 	// All browsers
1751 	disableBackspaceIntoATable();
1752 	removeBlockQuoteOnBackSpace();
1753 	emptyEditorWhenDeleting();
1754 
1755 	// WebKit
1756 	if (tinymce.isWebKit) {
1757 		keepInlineElementOnDeleteBackspace();
1758 		cleanupStylesWhenDeleting();
1759 		inputMethodFocus();
1760 		selectControlElements();
1761 		setDefaultBlockType();
1762 
1763 		// iOS
1764 		if (tinymce.isIDevice) {
1765 			selectionChangeNodeChanged();
1766 		} else {
1767 			fakeImageResize();
1768 			selectAll();
1769 		}
1770 	}
1771 
1772 	// IE
1773 	if (tinymce.isIE) {
1774 		removeHrOnBackspace();
1775 		ensureBodyHasRoleApplication();
1776 		addNewLinesBeforeBrInPre();
1777 		removePreSerializedStylesWhenSelectingControls();
1778 		deleteControlItemOnBackSpace();
1779 		renderEmptyBlocksFix();
1780 	}
1781 
1782 	// Gecko
1783 	if (tinymce.isGecko) {
1784 		removeHrOnBackspace();
1785 		focusBody();
1786 		removeStylesWhenDeletingAccrossBlockElements();
1787 		setGeckoEditingOptions();
1788 		addBrAfterLastLinks();
1789 		removeGhostSelection();
1790 	}
1791 };
1792 (function(tinymce) {
1793 	var namedEntities, baseEntities, reverseEntities,
1794 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1795 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1796 		rawCharsRegExp = /[<>&\"\']/g,
1797 		entityRegExp = /&(#x|#)?([\w]+);/g,
1798 		asciiMap = {
1799 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1800 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1801 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1802 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1803 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1804 		};
1805 
1806 	// Raw entities
1807 	baseEntities = {
1808 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
1809 		"'" : ''',
1810 		'<' : '<',
1811 		'>' : '>',
1812 		'&' : '&'
1813 	};
1814 
1815 	// Reverse lookup table for raw entities
1816 	reverseEntities = {
1817 		'<' : '<',
1818 		'>' : '>',
1819 		'&' : '&',
1820 		'"' : '"',
1821 		''' : "'"
1822 	};
1823 
1824 	// Decodes text by using the browser
1825 	function nativeDecode(text) {
1826 		var elm;
1827 
1828 		elm = document.createElement("div");
1829 		elm.innerHTML = text;
1830 
1831 		return elm.textContent || elm.innerText || text;
1832 	};
1833 
1834 	// Build a two way lookup table for the entities
1835 	function buildEntitiesLookup(items, radix) {
1836 		var i, chr, entity, lookup = {};
1837 
1838 		if (items) {
1839 			items = items.split(',');
1840 			radix = radix || 10;
1841 
1842 			// Build entities lookup table
1843 			for (i = 0; i < items.length; i += 2) {
1844 				chr = String.fromCharCode(parseInt(items[i], radix));
1845 
1846 				// Only add non base entities
1847 				if (!baseEntities[chr]) {
1848 					entity = '&' + items[i + 1] + ';';
1849 					lookup[chr] = entity;
1850 					lookup[entity] = chr;
1851 				}
1852 			}
1853 
1854 			return lookup;
1855 		}
1856 	};
1857 
1858 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1859 	namedEntities = buildEntitiesLookup(
1860 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1861 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1862 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1863 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1864 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1865 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1866 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1867 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1868 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1869 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1870 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1871 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1872 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1873 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1874 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1875 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1876 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1877 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1878 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1879 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1880 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1881 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1882 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1883 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1884 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
1885 
1886 	tinymce.html = tinymce.html || {};
1887 
1888 	tinymce.html.Entities = {
1889 		encodeRaw : function(text, attr) {
1890 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1891 				return baseEntities[chr] || chr;
1892 			});
1893 		},
1894 
1895 		encodeAllRaw : function(text) {
1896 			return ('' + text).replace(rawCharsRegExp, function(chr) {
1897 				return baseEntities[chr] || chr;
1898 			});
1899 		},
1900 
1901 		encodeNumeric : function(text, attr) {
1902 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1903 				// Multi byte sequence convert it to a single entity
1904 				if (chr.length > 1)
1905 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1906 
1907 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1908 			});
1909 		},
1910 
1911 		encodeNamed : function(text, attr, entities) {
1912 			entities = entities || namedEntities;
1913 
1914 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1915 				return baseEntities[chr] || entities[chr] || chr;
1916 			});
1917 		},
1918 
1919 		getEncodeFunc : function(name, entities) {
1920 			var Entities = tinymce.html.Entities;
1921 
1922 			entities = buildEntitiesLookup(entities) || namedEntities;
1923 
1924 			function encodeNamedAndNumeric(text, attr) {
1925 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1926 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1927 				});
1928 			};
1929 
1930 			function encodeCustomNamed(text, attr) {
1931 				return Entities.encodeNamed(text, attr, entities);
1932 			};
1933 
1934 			// Replace + with , to be compatible with previous TinyMCE versions
1935 			name = tinymce.makeMap(name.replace(/\+/g, ','));
1936 
1937 			// Named and numeric encoder
1938 			if (name.named && name.numeric)
1939 				return encodeNamedAndNumeric;
1940 
1941 			// Named encoder
1942 			if (name.named) {
1943 				// Custom names
1944 				if (entities)
1945 					return encodeCustomNamed;
1946 
1947 				return Entities.encodeNamed;
1948 			}
1949 
1950 			// Numeric
1951 			if (name.numeric)
1952 				return Entities.encodeNumeric;
1953 
1954 			// Raw encoder
1955 			return Entities.encodeRaw;
1956 		},
1957 
1958 		decode : function(text) {
1959 			return text.replace(entityRegExp, function(all, numeric, value) {
1960 				if (numeric) {
1961 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1962 
1963 					// Support upper UTF
1964 					if (value > 0xFFFF) {
1965 						value -= 0x10000;
1966 
1967 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1968 					} else
1969 						return asciiMap[value] || String.fromCharCode(value);
1970 				}
1971 
1972 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1973 			});
1974 		}
1975 	};
1976 })(tinymce);
1977 
1978 tinymce.html.Styles = function(settings, schema) {
1979 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1980 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1981 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1982 		trimRightRegExp = /\s+$/,
1983 		urlColorRegExp = /rgb/,
1984 		undef, i, encodingLookup = {}, encodingItems;
1985 
1986 	settings = settings || {};
1987 
1988 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1989 	for (i = 0; i < encodingItems.length; i++) {
1990 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1991 		encodingLookup['\uFEFF' + i] = encodingItems[i];
1992 	}
1993 
1994 	function toHex(match, r, g, b) {
1995 		function hex(val) {
1996 			val = parseInt(val).toString(16);
1997 
1998 			return val.length > 1 ? val : '0' + val; // 0 -> 00
1999 		};
2000 
2001 		return '#' + hex(r) + hex(g) + hex(b);
2002 	};
2003 
2004 	return {
2005 		toHex : function(color) {
2006 			return color.replace(rgbRegExp, toHex);
2007 		},
2008 
2009 		parse : function(css) {
2010 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2011 
2012 			function compress(prefix, suffix) {
2013 				var top, right, bottom, left;
2014 
2015 				// Get values and check it it needs compressing
2016 				top = styles[prefix + '-top' + suffix];
2017 				if (!top)
2018 					return;
2019 
2020 				right = styles[prefix + '-right' + suffix];
2021 				if (top != right)
2022 					return;
2023 
2024 				bottom = styles[prefix + '-bottom' + suffix];
2025 				if (right != bottom)
2026 					return;
2027 
2028 				left = styles[prefix + '-left' + suffix];
2029 				if (bottom != left)
2030 					return;
2031 
2032 				// Compress
2033 				styles[prefix + suffix] = left;
2034 				delete styles[prefix + '-top' + suffix];
2035 				delete styles[prefix + '-right' + suffix];
2036 				delete styles[prefix + '-bottom' + suffix];
2037 				delete styles[prefix + '-left' + suffix];
2038 			};
2039 
2040 			function canCompress(key) {
2041 				var value = styles[key], i;
2042 
2043 				if (!value || value.indexOf(' ') < 0)
2044 					return;
2045 
2046 				value = value.split(' ');
2047 				i = value.length;
2048 				while (i--) {
2049 					if (value[i] !== value[0])
2050 						return false;
2051 				}
2052 
2053 				styles[key] = value[0];
2054 
2055 				return true;
2056 			};
2057 
2058 			function compress2(target, a, b, c) {
2059 				if (!canCompress(a))
2060 					return;
2061 
2062 				if (!canCompress(b))
2063 					return;
2064 
2065 				if (!canCompress(c))
2066 					return;
2067 
2068 				// Compress
2069 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2070 				delete styles[a];
2071 				delete styles[b];
2072 				delete styles[c];
2073 			};
2074 
2075 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2076 			function encode(str) {
2077 				isEncoded = true;
2078 
2079 				return encodingLookup[str];
2080 			};
2081 
2082 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2083 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2084 			function decode(str, keep_slashes) {
2085 				if (isEncoded) {
2086 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2087 						return encodingLookup[str];
2088 					});
2089 				}
2090 
2091 				if (!keep_slashes)
2092 					str = str.replace(/\\([\'\";:])/g, "$1");
2093 
2094 				return str;
2095 			};
2096 
2097 			function processUrl(match, url, url2, url3, str, str2) {
2098 				str = str || str2;
2099 
2100 				if (str) {
2101 					str = decode(str);
2102 
2103 					// Force strings into single quote format
2104 					return "'" + str.replace(/\'/g, "\\'") + "'";
2105 				}
2106 
2107 				url = decode(url || url2 || url3);
2108 
2109 				// Convert the URL to relative/absolute depending on config
2110 				if (urlConverter)
2111 					url = urlConverter.call(urlConverterScope, url, 'style');
2112 
2113 				// Output new URL format
2114 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2115 			};
2116 
2117 			if (css) {
2118 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2119 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2120 					return str.replace(/[;:]/g, encode);
2121 				});
2122 
2123 				// Parse styles
2124 				while (matches = styleRegExp.exec(css)) {
2125 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2126 					value = matches[2].replace(trimRightRegExp, '');
2127 
2128 					if (name && value.length > 0) {
2129 						// Opera will produce 700 instead of bold in their style values
2130 						if (name === 'font-weight' && value === '700')
2131 							value = 'bold';
2132 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2133 							value = value.toLowerCase();		
2134 
2135 						// Convert RGB colors to HEX
2136 						value = value.replace(rgbRegExp, toHex);
2137 
2138 						// Convert URLs and force them into url('value') format
2139 						value = value.replace(urlOrStrRegExp, processUrl);
2140 						styles[name] = isEncoded ? decode(value, true) : value;
2141 					}
2142 
2143 					styleRegExp.lastIndex = matches.index + matches[0].length;
2144 				}
2145 
2146 				// Compress the styles to reduce it's size for example IE will expand styles
2147 				compress("border", "");
2148 				compress("border", "-width");
2149 				compress("border", "-color");
2150 				compress("border", "-style");
2151 				compress("padding", "");
2152 				compress("margin", "");
2153 				compress2('border', 'border-width', 'border-style', 'border-color');
2154 
2155 				// Remove pointless border, IE produces these
2156 				if (styles.border === 'medium none')
2157 					delete styles.border;
2158 			}
2159 
2160 			return styles;
2161 		},
2162 
2163 		serialize : function(styles, element_name) {
2164 			var css = '', name, value;
2165 
2166 			function serializeStyles(name) {
2167 				var styleList, i, l, value;
2168 
2169 				styleList = schema.styles[name];
2170 				if (styleList) {
2171 					for (i = 0, l = styleList.length; i < l; i++) {
2172 						name = styleList[i];
2173 						value = styles[name];
2174 
2175 						if (value !== undef && value.length > 0)
2176 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2177 					}
2178 				}
2179 			};
2180 
2181 			// Serialize styles according to schema
2182 			if (element_name && schema && schema.styles) {
2183 				// Serialize global styles and element specific styles
2184 				serializeStyles('*');
2185 				serializeStyles(element_name);
2186 			} else {
2187 				// Output the styles in the order they are inside the object
2188 				for (name in styles) {
2189 					value = styles[name];
2190 
2191 					if (value !== undef && value.length > 0)
2192 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2193 				}
2194 			}
2195 
2196 			return css;
2197 		}
2198 	};
2199 };
2200 
2201 (function(tinymce) {
2202 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2203 
2204 	function split(str, delim) {
2205 		return str.split(delim || ',');
2206 	};
2207 
2208 	function unpack(lookup, data) {
2209 		var key, elements = {};
2210 
2211 		function replace(value) {
2212 			return value.replace(/[A-Z]+/g, function(key) {
2213 				return replace(lookup[key]);
2214 			});
2215 		};
2216 
2217 		// Unpack lookup
2218 		for (key in lookup) {
2219 			if (lookup.hasOwnProperty(key))
2220 				lookup[key] = replace(lookup[key]);
2221 		}
2222 
2223 		// Unpack and parse data into object map
2224 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2225 			attributes = split(attributes, '|');
2226 
2227 			elements[name] = {
2228 				attributes : makeMap(attributes),
2229 				attributesOrder : attributes,
2230 				children : makeMap(children, '|', {'#comment' : {}})
2231 			}
2232 		});
2233 
2234 		return elements;
2235 	};
2236 
2237 	function getHTML5() {
2238 		var html5 = mapCache.html5;
2239 
2240 		if (!html5) {
2241 			html5 = mapCache.html5 = unpack({
2242 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2243 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2244 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2245 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2246 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2247 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2248 				}, 'html[A|manifest][body|head]' +
2249 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2250 					'title[A][#]' +
2251 					'base[A|href|target][]' +
2252 					'link[A|href|rel|media|type|sizes][]' +
2253 					'meta[A|http-equiv|name|content|charset][]' +
2254 					'style[A|type|media|scoped][#]' +
2255 					'script[A|charset|type|src|defer|async][#]' +
2256 					'noscript[A][C]' +
2257 					'body[A][C]' +
2258 					'section[A][C]' +
2259 					'nav[A][C]' +
2260 					'article[A][C]' +
2261 					'aside[A][C]' +
2262 					'h1[A][B]' +
2263 					'h2[A][B]' +
2264 					'h3[A][B]' +
2265 					'h4[A][B]' +
2266 					'h5[A][B]' +
2267 					'h6[A][B]' +
2268 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2269 					'header[A][C]' +
2270 					'footer[A][C]' +
2271 					'address[A][C]' +
2272 					'p[A][B]' +
2273 					'br[A][]' +
2274 					'pre[A][B]' +
2275 					'dialog[A][dd|dt]' +
2276 					'blockquote[A|cite][C]' +
2277 					'ol[A|start|reversed][li]' +
2278 					'ul[A][li]' +
2279 					'li[A|value][C]' +
2280 					'dl[A][dd|dt]' +
2281 					'dt[A][B]' +
2282 					'dd[A][C]' +
2283 					'a[A|href|target|ping|rel|media|type][B]' +
2284 					'em[A][B]' +
2285 					'strong[A][B]' +
2286 					'small[A][B]' +
2287 					'cite[A][B]' +
2288 					'q[A|cite][B]' +
2289 					'dfn[A][B]' +
2290 					'abbr[A][B]' +
2291 					'code[A][B]' +
2292 					'var[A][B]' +
2293 					'samp[A][B]' +
2294 					'kbd[A][B]' +
2295 					'sub[A][B]' +
2296 					'sup[A][B]' +
2297 					'i[A][B]' +
2298 					'b[A][B]' +
2299 					'mark[A][B]' +
2300 					'progress[A|value|max][B]' +
2301 					'meter[A|value|min|max|low|high|optimum][B]' +
2302 					'time[A|datetime][B]' +
2303 					'ruby[A][B|rt|rp]' +
2304 					'rt[A][B]' +
2305 					'rp[A][B]' +
2306 					'bdo[A][B]' +
2307 					'span[A][B]' +
2308 					'ins[A|cite|datetime][B]' +
2309 					'del[A|cite|datetime][B]' +
2310 					'figure[A][C|legend|figcaption]' +
2311 					'figcaption[A][C]' +
2312 					'img[A|alt|src|height|width|usemap|ismap][]' +
2313 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2314 					'embed[A|src|height|width|type][]' +
2315 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2316 					'param[A|name|value][]' +
2317 					'details[A|open][C|legend]' +
2318 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2319 					'menu[A|type|label][C|li]' +
2320 					'legend[A][C|B]' +
2321 					'div[A][C]' +
2322 					'source[A|src|type|media][]' +
2323 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2324 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2325 					'hr[A][]' +
2326 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2327 					'fieldset[A|disabled|form|name][C|legend]' +
2328 					'label[A|form|for][B]' +
2329 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2330 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2331 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2332 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2333 					'datalist[A][B|option]' +
2334 					'optgroup[A|disabled|label][option]' +
2335 					'option[A|disabled|selected|label|value][]' +
2336 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2337 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2338 					'output[A|for|form|name][B]' +
2339 					'canvas[A|width|height][]' +
2340 					'map[A|name][B|C]' +
2341 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2342 					'mathml[A][]' +
2343 					'svg[A][]' +
2344 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2345 					'caption[A][C]' +
2346 					'colgroup[A|span][col]' +
2347 					'col[A|span][]' +
2348 					'thead[A][tr]' +
2349 					'tfoot[A][tr]' +
2350 					'tbody[A][tr]' +
2351 					'tr[A][th|td]' +
2352 					'th[A|headers|rowspan|colspan|scope][B]' +
2353 					'td[A|headers|rowspan|colspan][C]' +
2354 					'wbr[A][]'
2355 			);
2356 		}
2357 
2358 		return html5;
2359 	};
2360 
2361 	function getHTML4() {
2362 		var html4 = mapCache.html4;
2363 
2364 		if (!html4) {
2365 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2366 			html4 = mapCache.html4 = unpack({
2367 				Z : 'H|K|N|O|P',
2368 				Y : 'X|form|R|Q',
2369 				ZG : 'E|span|width|align|char|charoff|valign',
2370 				X : 'p|T|div|U|W|isindex|fieldset|table',
2371 				ZF : 'E|align|char|charoff|valign',
2372 				W : 'pre|hr|blockquote|address|center|noframes',
2373 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2374 				ZD : '[E][S]',
2375 				U : 'ul|ol|dl|menu|dir',
2376 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2377 				T : 'h1|h2|h3|h4|h5|h6',
2378 				ZB : 'X|S|Q',
2379 				S : 'R|P',
2380 				ZA : 'a|G|J|M|O|P',
2381 				R : 'a|H|K|N|O',
2382 				Q : 'noscript|P',
2383 				P : 'ins|del|script',
2384 				O : 'input|select|textarea|label|button',
2385 				N : 'M|L',
2386 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2387 				L : 'sub|sup',
2388 				K : 'J|I',
2389 				J : 'tt|i|b|u|s|strike',
2390 				I : 'big|small|font|basefont',
2391 				H : 'G|F',
2392 				G : 'br|span|bdo',
2393 				F : 'object|applet|img|map|iframe',
2394 				E : 'A|B|C',
2395 				D : 'accesskey|tabindex|onfocus|onblur',
2396 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2397 				B : 'lang|xml:lang|dir',
2398 				A : 'id|class|style|title'
2399 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2400 				'style[B|id|type|media|title|xml:space][]' + 
2401 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2402 				'param[id|name|value|valuetype|type][]' + 
2403 				'p[E|align][#|S]' + 
2404 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2405 				'br[A|clear][]' + 
2406 				'span[E][#|S]' + 
2407 				'bdo[A|C|B][#|S]' + 
2408 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2409 				'h1[E|align][#|S]' + 
2410 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2411 				'map[B|C|A|name][X|form|Q|area]' + 
2412 				'h2[E|align][#|S]' + 
2413 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2414 				'h3[E|align][#|S]' + 
2415 				'tt[E][#|S]' + 
2416 				'i[E][#|S]' + 
2417 				'b[E][#|S]' + 
2418 				'u[E][#|S]' + 
2419 				's[E][#|S]' + 
2420 				'strike[E][#|S]' + 
2421 				'big[E][#|S]' + 
2422 				'small[E][#|S]' + 
2423 				'font[A|B|size|color|face][#|S]' + 
2424 				'basefont[id|size|color|face][]' + 
2425 				'em[E][#|S]' + 
2426 				'strong[E][#|S]' + 
2427 				'dfn[E][#|S]' + 
2428 				'code[E][#|S]' + 
2429 				'q[E|cite][#|S]' + 
2430 				'samp[E][#|S]' + 
2431 				'kbd[E][#|S]' + 
2432 				'var[E][#|S]' + 
2433 				'cite[E][#|S]' + 
2434 				'abbr[E][#|S]' + 
2435 				'acronym[E][#|S]' + 
2436 				'sub[E][#|S]' + 
2437 				'sup[E][#|S]' + 
2438 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2439 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2440 				'optgroup[E|disabled|label][option]' + 
2441 				'option[E|selected|disabled|label|value][]' + 
2442 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2443 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2444 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2445 				'h4[E|align][#|S]' + 
2446 				'ins[E|cite|datetime][#|Y]' + 
2447 				'h5[E|align][#|S]' + 
2448 				'del[E|cite|datetime][#|Y]' + 
2449 				'h6[E|align][#|S]' + 
2450 				'div[E|align][#|Y]' + 
2451 				'ul[E|type|compact][li]' + 
2452 				'li[E|type|value][#|Y]' + 
2453 				'ol[E|type|compact|start][li]' + 
2454 				'dl[E|compact][dt|dd]' + 
2455 				'dt[E][#|S]' + 
2456 				'dd[E][#|Y]' + 
2457 				'menu[E|compact][li]' + 
2458 				'dir[E|compact][li]' + 
2459 				'pre[E|width|xml:space][#|ZA]' + 
2460 				'hr[E|align|noshade|size|width][]' + 
2461 				'blockquote[E|cite][#|Y]' + 
2462 				'address[E][#|S|p]' + 
2463 				'center[E][#|Y]' + 
2464 				'noframes[E][#|Y]' + 
2465 				'isindex[A|B|prompt][]' + 
2466 				'fieldset[E][#|legend|Y]' + 
2467 				'legend[E|accesskey|align][#|S]' + 
2468 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2469 				'caption[E|align][#|S]' + 
2470 				'col[ZG][]' + 
2471 				'colgroup[ZG][col]' + 
2472 				'thead[ZF][tr]' + 
2473 				'tr[ZF|bgcolor][th|td]' + 
2474 				'th[E|ZE][#|Y]' + 
2475 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2476 				'noscript[E][#|Y]' + 
2477 				'td[E|ZE][#|Y]' + 
2478 				'tfoot[ZF][tr]' + 
2479 				'tbody[ZF][tr]' + 
2480 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2481 				'base[id|href|target][]' + 
2482 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2483 			);
2484 		}
2485 
2486 		return html4;
2487 	};
2488 
2489 	tinymce.html.Schema = function(settings) {
2490 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2491 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2492 
2493 		// Creates an lookup table map object for the specified option or the default value
2494 		function createLookupTable(option, default_value, extend) {
2495 			var value = settings[option];
2496 
2497 			if (!value) {
2498 				// Get cached default map or make it if needed
2499 				value = mapCache[option];
2500 
2501 				if (!value) {
2502 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2503 					value = tinymce.extend(value, extend);
2504 
2505 					mapCache[option] = value;
2506 				}
2507 			} else {
2508 				// Create custom map
2509 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2510 			}
2511 
2512 			return value;
2513 		};
2514 
2515 		settings = settings || {};
2516 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2517 
2518 		// Allow all elements and attributes if verify_html is set to false
2519 		if (settings.verify_html === false)
2520 			settings.valid_elements = '*[*]';
2521 
2522 		// Build styles list
2523 		if (settings.valid_styles) {
2524 			validStyles = {};
2525 
2526 			// Convert styles into a rule list
2527 			each(settings.valid_styles, function(value, key) {
2528 				validStyles[key] = tinymce.explode(value);
2529 			});
2530 		}
2531 
2532 		// Setup map objects
2533 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2534 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2535 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2536 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2537 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2538 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2539 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2540 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2541 
2542 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2543 		function patternToRegExp(str) {
2544 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2545 		};
2546 
2547 		// Parses the specified valid_elements string and adds to the current rules
2548 		// This function is a bit hard to read since it's heavily optimized for speed
2549 		function addValidElements(valid_elements) {
2550 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2551 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2552 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2553 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2554 				hasPatternsRegExp = /[*?+]/;
2555 
2556 			if (valid_elements) {
2557 				// Split valid elements into an array with rules
2558 				valid_elements = split(valid_elements);
2559 
2560 				if (elements['@']) {
2561 					globalAttributes = elements['@'].attributes;
2562 					globalAttributesOrder = elements['@'].attributesOrder;
2563 				}
2564 
2565 				// Loop all rules
2566 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2567 					// Parse element rule
2568 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2569 					if (matches) {
2570 						// Setup local names for matches
2571 						prefix = matches[1];
2572 						elementName = matches[2];
2573 						outputName = matches[3];
2574 						attrData = matches[4];
2575 
2576 						// Create new attributes and attributesOrder
2577 						attributes = {};
2578 						attributesOrder = [];
2579 
2580 						// Create the new element
2581 						element = {
2582 							attributes : attributes,
2583 							attributesOrder : attributesOrder
2584 						};
2585 
2586 						// Padd empty elements prefix
2587 						if (prefix === '#')
2588 							element.paddEmpty = true;
2589 
2590 						// Remove empty elements prefix
2591 						if (prefix === '-')
2592 							element.removeEmpty = true;
2593 
2594 						// Copy attributes from global rule into current rule
2595 						if (globalAttributes) {
2596 							for (key in globalAttributes)
2597 								attributes[key] = globalAttributes[key];
2598 
2599 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2600 						}
2601 
2602 						// Attributes defined
2603 						if (attrData) {
2604 							attrData = split(attrData, '|');
2605 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2606 								matches = attrRuleRegExp.exec(attrData[ai]);
2607 								if (matches) {
2608 									attr = {};
2609 									attrType = matches[1];
2610 									attrName = matches[2].replace(/::/g, ':');
2611 									prefix = matches[3];
2612 									value = matches[4];
2613 
2614 									// Required
2615 									if (attrType === '!') {
2616 										element.attributesRequired = element.attributesRequired || [];
2617 										element.attributesRequired.push(attrName);
2618 										attr.required = true;
2619 									}
2620 
2621 									// Denied from global
2622 									if (attrType === '-') {
2623 										delete attributes[attrName];
2624 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2625 										continue;
2626 									}
2627 
2628 									// Default value
2629 									if (prefix) {
2630 										// Default value
2631 										if (prefix === '=') {
2632 											element.attributesDefault = element.attributesDefault || [];
2633 											element.attributesDefault.push({name: attrName, value: value});
2634 											attr.defaultValue = value;
2635 										}
2636 
2637 										// Forced value
2638 										if (prefix === ':') {
2639 											element.attributesForced = element.attributesForced || [];
2640 											element.attributesForced.push({name: attrName, value: value});
2641 											attr.forcedValue = value;
2642 										}
2643 
2644 										// Required values
2645 										if (prefix === '<')
2646 											attr.validValues = makeMap(value, '?');
2647 									}
2648 
2649 									// Check for attribute patterns
2650 									if (hasPatternsRegExp.test(attrName)) {
2651 										element.attributePatterns = element.attributePatterns || [];
2652 										attr.pattern = patternToRegExp(attrName);
2653 										element.attributePatterns.push(attr);
2654 									} else {
2655 										// Add attribute to order list if it doesn't already exist
2656 										if (!attributes[attrName])
2657 											attributesOrder.push(attrName);
2658 
2659 										attributes[attrName] = attr;
2660 									}
2661 								}
2662 							}
2663 						}
2664 
2665 						// Global rule, store away these for later usage
2666 						if (!globalAttributes && elementName == '@') {
2667 							globalAttributes = attributes;
2668 							globalAttributesOrder = attributesOrder;
2669 						}
2670 
2671 						// Handle substitute elements such as b/strong
2672 						if (outputName) {
2673 							element.outputName = elementName;
2674 							elements[outputName] = element;
2675 						}
2676 
2677 						// Add pattern or exact element
2678 						if (hasPatternsRegExp.test(elementName)) {
2679 							element.pattern = patternToRegExp(elementName);
2680 							patternElements.push(element);
2681 						} else
2682 							elements[elementName] = element;
2683 					}
2684 				}
2685 			}
2686 		};
2687 
2688 		function setValidElements(valid_elements) {
2689 			elements = {};
2690 			patternElements = [];
2691 
2692 			addValidElements(valid_elements);
2693 
2694 			each(schemaItems, function(element, name) {
2695 				children[name] = element.children;
2696 			});
2697 		};
2698 
2699 		// Adds custom non HTML elements to the schema
2700 		function addCustomElements(custom_elements) {
2701 			var customElementRegExp = /^(~)?(.+)$/;
2702 
2703 			if (custom_elements) {
2704 				each(split(custom_elements), function(rule) {
2705 					var matches = customElementRegExp.exec(rule),
2706 						inline = matches[1] === '~',
2707 						cloneName = inline ? 'span' : 'div',
2708 						name = matches[2];
2709 
2710 					children[name] = children[cloneName];
2711 					customElementsMap[name] = cloneName;
2712 
2713 					// If it's not marked as inline then add it to valid block elements
2714 					if (!inline)
2715 						blockElementsMap[name] = {};
2716 
2717 					// Add custom elements at span/div positions
2718 					each(children, function(element, child) {
2719 						if (element[cloneName])
2720 							element[name] = element[cloneName];
2721 					});
2722 				});
2723 			}
2724 		};
2725 
2726 		// Adds valid children to the schema object
2727 		function addValidChildren(valid_children) {
2728 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2729 
2730 			if (valid_children) {
2731 				each(split(valid_children), function(rule) {
2732 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2733 
2734 					if (matches) {
2735 						prefix = matches[1];
2736 
2737 						// Add/remove items from default
2738 						if (prefix)
2739 							parent = children[matches[2]];
2740 						else
2741 							parent = children[matches[2]] = {'#comment' : {}};
2742 
2743 						parent = children[matches[2]];
2744 
2745 						each(split(matches[3], '|'), function(child) {
2746 							if (prefix === '-')
2747 								delete parent[child];
2748 							else
2749 								parent[child] = {};
2750 						});
2751 					}
2752 				});
2753 			}
2754 		};
2755 
2756 		function getElementRule(name) {
2757 			var element = elements[name], i;
2758 
2759 			// Exact match found
2760 			if (element)
2761 				return element;
2762 
2763 			// No exact match then try the patterns
2764 			i = patternElements.length;
2765 			while (i--) {
2766 				element = patternElements[i];
2767 
2768 				if (element.pattern.test(name))
2769 					return element;
2770 			}
2771 		};
2772 
2773 		if (!settings.valid_elements) {
2774 			// No valid elements defined then clone the elements from the schema spec
2775 			each(schemaItems, function(element, name) {
2776 				elements[name] = {
2777 					attributes : element.attributes,
2778 					attributesOrder : element.attributesOrder
2779 				};
2780 
2781 				children[name] = element.children;
2782 			});
2783 
2784 			// Switch these on HTML4
2785 			if (settings.schema != "html5") {
2786 				each(split('strong/b,em/i'), function(item) {
2787 					item = split(item, '/');
2788 					elements[item[1]].outputName = item[0];
2789 				});
2790 			}
2791 
2792 			// Add default alt attribute for images
2793 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2794 
2795 			// Remove these if they are empty by default
2796 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
2797 				if (elements[name]) {
2798 					elements[name].removeEmpty = true;
2799 				}
2800 			});
2801 
2802 			// Padd these by default
2803 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2804 				elements[name].paddEmpty = true;
2805 			});
2806 		} else
2807 			setValidElements(settings.valid_elements);
2808 
2809 		addCustomElements(settings.custom_elements);
2810 		addValidChildren(settings.valid_children);
2811 		addValidElements(settings.extended_valid_elements);
2812 
2813 		// Todo: Remove this when we fix list handling to be valid
2814 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2815 
2816 		// Delete invalid elements
2817 		if (settings.invalid_elements) {
2818 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2819 				if (elements[item])
2820 					delete elements[item];
2821 			});
2822 		}
2823 
2824 		// If the user didn't allow span only allow internal spans
2825 		if (!getElementRule('span'))
2826 			addValidElements('span[!data-mce-type|*]');
2827 
2828 		self.children = children;
2829 
2830 		self.styles = validStyles;
2831 
2832 		self.getBoolAttrs = function() {
2833 			return boolAttrMap;
2834 		};
2835 
2836 		self.getBlockElements = function() {
2837 			return blockElementsMap;
2838 		};
2839 
2840 		self.getShortEndedElements = function() {
2841 			return shortEndedElementsMap;
2842 		};
2843 
2844 		self.getSelfClosingElements = function() {
2845 			return selfClosingElementsMap;
2846 		};
2847 
2848 		self.getNonEmptyElements = function() {
2849 			return nonEmptyElementsMap;
2850 		};
2851 
2852 		self.getWhiteSpaceElements = function() {
2853 			return whiteSpaceElementsMap;
2854 		};
2855 
2856 		self.isValidChild = function(name, child) {
2857 			var parent = children[name];
2858 
2859 			return !!(parent && parent[child]);
2860 		};
2861 
2862 		self.isValid = function(name, attr) {
2863 			var attrPatterns, i, rule = getElementRule(name);
2864 
2865 			// Check if it's a valid element
2866 			if (rule) {
2867 				if (attr) {
2868 					// Check if attribute name exists
2869 					if (rule.attributes[attr]) {
2870 						return true;
2871 					}
2872 
2873 					// Check if attribute matches a regexp pattern
2874 					attrPatterns = rule.attributePatterns;
2875 					if (attrPatterns) {
2876 						i = attrPatterns.length;
2877 						while (i--) {
2878 							if (attrPatterns[i].pattern.test(name)) {
2879 								return true;
2880 							}
2881 						}
2882 					}
2883 				} else {
2884 					return true;
2885 				}
2886 			}
2887 
2888 			// No match
2889 			return false;
2890 		};
2891 		
2892 		self.getElementRule = getElementRule;
2893 
2894 		self.getCustomElements = function() {
2895 			return customElementsMap;
2896 		};
2897 
2898 		self.addValidElements = addValidElements;
2899 
2900 		self.setValidElements = setValidElements;
2901 
2902 		self.addCustomElements = addCustomElements;
2903 
2904 		self.addValidChildren = addValidChildren;
2905 	};
2906 })(tinymce);
2907 
2908 (function(tinymce) {
2909 	tinymce.html.SaxParser = function(settings, schema) {
2910 		var self = this, noop = function() {};
2911 
2912 		settings = settings || {};
2913 		self.schema = schema = schema || new tinymce.html.Schema();
2914 
2915 		if (settings.fix_self_closing !== false)
2916 			settings.fix_self_closing = true;
2917 
2918 		// Add handler functions from settings and setup default handlers
2919 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2920 			if (name)
2921 				self[name] = settings[name] || noop;
2922 		});
2923 
2924 		self.parse = function(html) {
2925 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2926 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2927 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2928 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2929 
2930 			function processEndTag(name) {
2931 				var pos, i;
2932 
2933 				// Find position of parent of the same type
2934 				pos = stack.length;
2935 				while (pos--) {
2936 					if (stack[pos].name === name)
2937 						break;						
2938 				}
2939 
2940 				// Found parent
2941 				if (pos >= 0) {
2942 					// Close all the open elements
2943 					for (i = stack.length - 1; i >= pos; i--) {
2944 						name = stack[i];
2945 
2946 						if (name.valid)
2947 							self.end(name.name);
2948 					}
2949 
2950 					// Remove the open elements from the stack
2951 					stack.length = pos;
2952 				}
2953 			};
2954 
2955 			function parseAttribute(match, name, value, val2, val3) {
2956 				var attrRule, i;
2957 
2958 				name = name.toLowerCase();
2959 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2960 
2961 				// Validate name and value
2962 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2963 					attrRule = validAttributesMap[name];
2964 
2965 					// Find rule by pattern matching
2966 					if (!attrRule && validAttributePatterns) {
2967 						i = validAttributePatterns.length;
2968 						while (i--) {
2969 							attrRule = validAttributePatterns[i];
2970 							if (attrRule.pattern.test(name))
2971 								break;
2972 						}
2973 
2974 						// No rule matched
2975 						if (i === -1)
2976 							attrRule = null;
2977 					}
2978 
2979 					// No attribute rule found
2980 					if (!attrRule)
2981 						return;
2982 
2983 					// Validate value
2984 					if (attrRule.validValues && !(value in attrRule.validValues))
2985 						return;
2986 				}
2987 
2988 				// Add attribute to list and map
2989 				attrList.map[name] = value;
2990 				attrList.push({
2991 					name: name,
2992 					value: value
2993 				});
2994 			};
2995 
2996 			// Precompile RegExps and map objects
2997 			tokenRegExp = new RegExp('<(?:' +
2998 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2999 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3000 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3001 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3002 				'(?:\\/([^>]+)>)|' + // End element
3003 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3004 			')', 'g');
3005 
3006 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
3007 			specialElements = {
3008 				'script' : /<\/script[^>]*>/gi,
3009 				'style' : /<\/style[^>]*>/gi,
3010 				'noscript' : /<\/noscript[^>]*>/gi
3011 			};
3012 
3013 			// Setup lookup tables for empty elements and boolean attributes
3014 			shortEndedElements = schema.getShortEndedElements();
3015 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3016 			fillAttrsMap = schema.getBoolAttrs();
3017 			validate = settings.validate;
3018 			removeInternalElements = settings.remove_internals;
3019 			fixSelfClosing = settings.fix_self_closing;
3020 			isIE = tinymce.isIE;
3021 			invalidPrefixRegExp = /^:/;
3022 
3023 			while (matches = tokenRegExp.exec(html)) {
3024 				// Text
3025 				if (index < matches.index)
3026 					self.text(decode(html.substr(index, matches.index - index)));
3027 
3028 				if (value = matches[6]) { // End element
3029 					value = value.toLowerCase();
3030 
3031 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3032 					if (isIE && invalidPrefixRegExp.test(value))
3033 						value = value.substr(1);
3034 
3035 					processEndTag(value);
3036 				} else if (value = matches[7]) { // Start element
3037 					value = value.toLowerCase();
3038 
3039 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3040 					if (isIE && invalidPrefixRegExp.test(value))
3041 						value = value.substr(1);
3042 
3043 					isShortEnded = value in shortEndedElements;
3044 
3045 					// Is self closing tag for example an <li> after an open <li>
3046 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3047 						processEndTag(value);
3048 
3049 					// Validate element
3050 					if (!validate || (elementRule = schema.getElementRule(value))) {
3051 						isValidElement = true;
3052 
3053 						// Grab attributes map and patters when validation is enabled
3054 						if (validate) {
3055 							validAttributesMap = elementRule.attributes;
3056 							validAttributePatterns = elementRule.attributePatterns;
3057 						}
3058 
3059 						// Parse attributes
3060 						if (attribsValue = matches[8]) {
3061 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3062 
3063 							// If the element has internal attributes then remove it if we are told to do so
3064 							if (isInternalElement && removeInternalElements)
3065 								isValidElement = false;
3066 
3067 							attrList = [];
3068 							attrList.map = {};
3069 
3070 							attribsValue.replace(attrRegExp, parseAttribute);
3071 						} else {
3072 							attrList = [];
3073 							attrList.map = {};
3074 						}
3075 
3076 						// Process attributes if validation is enabled
3077 						if (validate && !isInternalElement) {
3078 							attributesRequired = elementRule.attributesRequired;
3079 							attributesDefault = elementRule.attributesDefault;
3080 							attributesForced = elementRule.attributesForced;
3081 
3082 							// Handle forced attributes
3083 							if (attributesForced) {
3084 								i = attributesForced.length;
3085 								while (i--) {
3086 									attr = attributesForced[i];
3087 									name = attr.name;
3088 									attrValue = attr.value;
3089 
3090 									if (attrValue === '{$uid}')
3091 										attrValue = 'mce_' + idCount++;
3092 
3093 									attrList.map[name] = attrValue;
3094 									attrList.push({name: name, value: attrValue});
3095 								}
3096 							}
3097 
3098 							// Handle default attributes
3099 							if (attributesDefault) {
3100 								i = attributesDefault.length;
3101 								while (i--) {
3102 									attr = attributesDefault[i];
3103 									name = attr.name;
3104 
3105 									if (!(name in attrList.map)) {
3106 										attrValue = attr.value;
3107 
3108 										if (attrValue === '{$uid}')
3109 											attrValue = 'mce_' + idCount++;
3110 
3111 										attrList.map[name] = attrValue;
3112 										attrList.push({name: name, value: attrValue});
3113 									}
3114 								}
3115 							}
3116 
3117 							// Handle required attributes
3118 							if (attributesRequired) {
3119 								i = attributesRequired.length;
3120 								while (i--) {
3121 									if (attributesRequired[i] in attrList.map)
3122 										break;
3123 								}
3124 
3125 								// None of the required attributes where found
3126 								if (i === -1)
3127 									isValidElement = false;
3128 							}
3129 
3130 							// Invalidate element if it's marked as bogus
3131 							if (attrList.map['data-mce-bogus'])
3132 								isValidElement = false;
3133 						}
3134 
3135 						if (isValidElement)
3136 							self.start(value, attrList, isShortEnded);
3137 					} else
3138 						isValidElement = false;
3139 
3140 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3141 					if (endRegExp = specialElements[value]) {
3142 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3143 
3144 						if (matches = endRegExp.exec(html)) {
3145 							if (isValidElement)
3146 								text = html.substr(index, matches.index - index);
3147 
3148 							index = matches.index + matches[0].length;
3149 						} else {
3150 							text = html.substr(index);
3151 							index = html.length;
3152 						}
3153 
3154 						if (isValidElement && text.length > 0)
3155 							self.text(text, true);
3156 
3157 						if (isValidElement)
3158 							self.end(value);
3159 
3160 						tokenRegExp.lastIndex = index;
3161 						continue;
3162 					}
3163 
3164 					// Push value on to stack
3165 					if (!isShortEnded) {
3166 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3167 							stack.push({name: value, valid: isValidElement});
3168 						else if (isValidElement)
3169 							self.end(value);
3170 					}
3171 				} else if (value = matches[1]) { // Comment
3172 					self.comment(value);
3173 				} else if (value = matches[2]) { // CDATA
3174 					self.cdata(value);
3175 				} else if (value = matches[3]) { // DOCTYPE
3176 					self.doctype(value);
3177 				} else if (value = matches[4]) { // PI
3178 					self.pi(value, matches[5]);
3179 				}
3180 
3181 				index = matches.index + matches[0].length;
3182 			}
3183 
3184 			// Text
3185 			if (index < html.length)
3186 				self.text(decode(html.substr(index)));
3187 
3188 			// Close any open elements
3189 			for (i = stack.length - 1; i >= 0; i--) {
3190 				value = stack[i];
3191 
3192 				if (value.valid)
3193 					self.end(value.name);
3194 			}
3195 		};
3196 	}
3197 })(tinymce);
3198 
3199 (function(tinymce) {
3200 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3201 		'#text' : 3,
3202 		'#comment' : 8,
3203 		'#cdata' : 4,
3204 		'#pi' : 7,
3205 		'#doctype' : 10,
3206 		'#document-fragment' : 11
3207 	};
3208 
3209 	// Walks the tree left/right
3210 	function walk(node, root_node, prev) {
3211 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3212 
3213 		// Walk into nodes if it has a start
3214 		if (node[startName])
3215 			return node[startName];
3216 
3217 		// Return the sibling if it has one
3218 		if (node !== root_node) {
3219 			sibling = node[siblingName];
3220 
3221 			if (sibling)
3222 				return sibling;
3223 
3224 			// Walk up the parents to look for siblings
3225 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3226 				sibling = parent[siblingName];
3227 
3228 				if (sibling)
3229 					return sibling;
3230 			}
3231 		}
3232 	};
3233 
3234 	function Node(name, type) {
3235 		this.name = name;
3236 		this.type = type;
3237 
3238 		if (type === 1) {
3239 			this.attributes = [];
3240 			this.attributes.map = {};
3241 		}
3242 	}
3243 
3244 	tinymce.extend(Node.prototype, {
3245 		replace : function(node) {
3246 			var self = this;
3247 
3248 			if (node.parent)
3249 				node.remove();
3250 
3251 			self.insert(node, self);
3252 			self.remove();
3253 
3254 			return self;
3255 		},
3256 
3257 		attr : function(name, value) {
3258 			var self = this, attrs, i, undef;
3259 
3260 			if (typeof name !== "string") {
3261 				for (i in name)
3262 					self.attr(i, name[i]);
3263 
3264 				return self;
3265 			}
3266 
3267 			if (attrs = self.attributes) {
3268 				if (value !== undef) {
3269 					// Remove attribute
3270 					if (value === null) {
3271 						if (name in attrs.map) {
3272 							delete attrs.map[name];
3273 
3274 							i = attrs.length;
3275 							while (i--) {
3276 								if (attrs[i].name === name) {
3277 									attrs = attrs.splice(i, 1);
3278 									return self;
3279 								}
3280 							}
3281 						}
3282 
3283 						return self;
3284 					}
3285 
3286 					// Set attribute
3287 					if (name in attrs.map) {
3288 						// Set attribute
3289 						i = attrs.length;
3290 						while (i--) {
3291 							if (attrs[i].name === name) {
3292 								attrs[i].value = value;
3293 								break;
3294 							}
3295 						}
3296 					} else
3297 						attrs.push({name: name, value: value});
3298 
3299 					attrs.map[name] = value;
3300 
3301 					return self;
3302 				} else {
3303 					return attrs.map[name];
3304 				}
3305 			}
3306 		},
3307 
3308 		clone : function() {
3309 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3310 
3311 			// Clone element attributes
3312 			if (selfAttrs = self.attributes) {
3313 				cloneAttrs = [];
3314 				cloneAttrs.map = {};
3315 
3316 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3317 					selfAttr = selfAttrs[i];
3318 
3319 					// Clone everything except id
3320 					if (selfAttr.name !== 'id') {
3321 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3322 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3323 					}
3324 				}
3325 
3326 				clone.attributes = cloneAttrs;
3327 			}
3328 
3329 			clone.value = self.value;
3330 			clone.shortEnded = self.shortEnded;
3331 
3332 			return clone;
3333 		},
3334 
3335 		wrap : function(wrapper) {
3336 			var self = this;
3337 
3338 			self.parent.insert(wrapper, self);
3339 			wrapper.append(self);
3340 
3341 			return self;
3342 		},
3343 
3344 		unwrap : function() {
3345 			var self = this, node, next;
3346 
3347 			for (node = self.firstChild; node; ) {
3348 				next = node.next;
3349 				self.insert(node, self, true);
3350 				node = next;
3351 			}
3352 
3353 			self.remove();
3354 		},
3355 
3356 		remove : function() {
3357 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3358 
3359 			if (parent) {
3360 				if (parent.firstChild === self) {
3361 					parent.firstChild = next;
3362 
3363 					if (next)
3364 						next.prev = null;
3365 				} else {
3366 					prev.next = next;
3367 				}
3368 
3369 				if (parent.lastChild === self) {
3370 					parent.lastChild = prev;
3371 
3372 					if (prev)
3373 						prev.next = null;
3374 				} else {
3375 					next.prev = prev;
3376 				}
3377 
3378 				self.parent = self.next = self.prev = null;
3379 			}
3380 
3381 			return self;
3382 		},
3383 
3384 		append : function(node) {
3385 			var self = this, last;
3386 
3387 			if (node.parent)
3388 				node.remove();
3389 
3390 			last = self.lastChild;
3391 			if (last) {
3392 				last.next = node;
3393 				node.prev = last;
3394 				self.lastChild = node;
3395 			} else
3396 				self.lastChild = self.firstChild = node;
3397 
3398 			node.parent = self;
3399 
3400 			return node;
3401 		},
3402 
3403 		insert : function(node, ref_node, before) {
3404 			var parent;
3405 
3406 			if (node.parent)
3407 				node.remove();
3408 
3409 			parent = ref_node.parent || this;
3410 
3411 			if (before) {
3412 				if (ref_node === parent.firstChild)
3413 					parent.firstChild = node;
3414 				else
3415 					ref_node.prev.next = node;
3416 
3417 				node.prev = ref_node.prev;
3418 				node.next = ref_node;
3419 				ref_node.prev = node;
3420 			} else {
3421 				if (ref_node === parent.lastChild)
3422 					parent.lastChild = node;
3423 				else
3424 					ref_node.next.prev = node;
3425 
3426 				node.next = ref_node.next;
3427 				node.prev = ref_node;
3428 				ref_node.next = node;
3429 			}
3430 
3431 			node.parent = parent;
3432 
3433 			return node;
3434 		},
3435 
3436 		getAll : function(name) {
3437 			var self = this, node, collection = [];
3438 
3439 			for (node = self.firstChild; node; node = walk(node, self)) {
3440 				if (node.name === name)
3441 					collection.push(node);
3442 			}
3443 
3444 			return collection;
3445 		},
3446 
3447 		empty : function() {
3448 			var self = this, nodes, i, node;
3449 
3450 			// Remove all children
3451 			if (self.firstChild) {
3452 				nodes = [];
3453 
3454 				// Collect the children
3455 				for (node = self.firstChild; node; node = walk(node, self))
3456 					nodes.push(node);
3457 
3458 				// Remove the children
3459 				i = nodes.length;
3460 				while (i--) {
3461 					node = nodes[i];
3462 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3463 				}
3464 			}
3465 
3466 			self.firstChild = self.lastChild = null;
3467 
3468 			return self;
3469 		},
3470 
3471 		isEmpty : function(elements) {
3472 			var self = this, node = self.firstChild, i, name;
3473 
3474 			if (node) {
3475 				do {
3476 					if (node.type === 1) {
3477 						// Ignore bogus elements
3478 						if (node.attributes.map['data-mce-bogus'])
3479 							continue;
3480 
3481 						// Keep empty elements like <img />
3482 						if (elements[node.name])
3483 							return false;
3484 
3485 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3486 						i = node.attributes.length;
3487 						while (i--) {
3488 							name = node.attributes[i].name;
3489 							if (name === "name" || name.indexOf('data-') === 0)
3490 								return false;
3491 						}
3492 					}
3493 
3494 					// Keep comments
3495 					if (node.type === 8)
3496 						return false;
3497 					
3498 					// Keep non whitespace text nodes
3499 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3500 						return false;
3501 				} while (node = walk(node, self));
3502 			}
3503 
3504 			return true;
3505 		},
3506 
3507 		walk : function(prev) {
3508 			return walk(this, null, prev);
3509 		}
3510 	});
3511 
3512 	tinymce.extend(Node, {
3513 		create : function(name, attrs) {
3514 			var node, attrName;
3515 
3516 			// Create node
3517 			node = new Node(name, typeLookup[name] || 1);
3518 
3519 			// Add attributes if needed
3520 			if (attrs) {
3521 				for (attrName in attrs)
3522 					node.attr(attrName, attrs[attrName]);
3523 			}
3524 
3525 			return node;
3526 		}
3527 	});
3528 
3529 	tinymce.html.Node = Node;
3530 })(tinymce);
3531 
3532 (function(tinymce) {
3533 	var Node = tinymce.html.Node;
3534 
3535 	tinymce.html.DomParser = function(settings, schema) {
3536 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3537 
3538 		settings = settings || {};
3539 		settings.validate = "validate" in settings ? settings.validate : true;
3540 		settings.root_name = settings.root_name || 'body';
3541 		self.schema = schema = schema || new tinymce.html.Schema();
3542 
3543 		function fixInvalidChildren(nodes) {
3544 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3545 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3546 
3547 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3548 			nonEmptyElements = schema.getNonEmptyElements();
3549 
3550 			for (ni = 0; ni < nodes.length; ni++) {
3551 				node = nodes[ni];
3552 
3553 				// Already removed
3554 				if (!node.parent)
3555 					continue;
3556 
3557 				// Get list of all parent nodes until we find a valid parent to stick the child into
3558 				parents = [node];
3559 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3560 					parents.push(parent);
3561 
3562 				// Found a suitable parent
3563 				if (parent && parents.length > 1) {
3564 					// Reverse the array since it makes looping easier
3565 					parents.reverse();
3566 
3567 					// Clone the related parent and insert that after the moved node
3568 					newParent = currentNode = self.filterNode(parents[0].clone());
3569 
3570 					// Start cloning and moving children on the left side of the target node
3571 					for (i = 0; i < parents.length - 1; i++) {
3572 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3573 							tempNode = self.filterNode(parents[i].clone());
3574 							currentNode.append(tempNode);
3575 						} else
3576 							tempNode = currentNode;
3577 
3578 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3579 							nextNode = childNode.next;
3580 							tempNode.append(childNode);
3581 							childNode = nextNode;
3582 						}
3583 
3584 						currentNode = tempNode;
3585 					}
3586 
3587 					if (!newParent.isEmpty(nonEmptyElements)) {
3588 						parent.insert(newParent, parents[0], true);
3589 						parent.insert(node, newParent);
3590 					} else {
3591 						parent.insert(node, parents[0], true);
3592 					}
3593 
3594 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3595 					parent = parents[0];
3596 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3597 						parent.empty().remove();
3598 					}
3599 				} else if (node.parent) {
3600 					// If it's an LI try to find a UL/OL for it or wrap it
3601 					if (node.name === 'li') {
3602 						sibling = node.prev;
3603 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3604 							sibling.append(node);
3605 							continue;
3606 						}
3607 
3608 						sibling = node.next;
3609 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3610 							sibling.insert(node, sibling.firstChild, true);
3611 							continue;
3612 						}
3613 
3614 						node.wrap(self.filterNode(new Node('ul', 1)));
3615 						continue;
3616 					}
3617 
3618 					// Try wrapping the element in a DIV
3619 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3620 						node.wrap(self.filterNode(new Node('div', 1)));
3621 					} else {
3622 						// We failed wrapping it, then remove or unwrap it
3623 						if (node.name === 'style' || node.name === 'script')
3624 							node.empty().remove();
3625 						else
3626 							node.unwrap();
3627 					}
3628 				}
3629 			}
3630 		};
3631 
3632 		self.filterNode = function(node) {
3633 			var i, name, list;
3634 
3635 			// Run element filters
3636 			if (name in nodeFilters) {
3637 				list = matchedNodes[name];
3638 
3639 				if (list)
3640 					list.push(node);
3641 				else
3642 					matchedNodes[name] = [node];
3643 			}
3644 
3645 			// Run attribute filters
3646 			i = attributeFilters.length;
3647 			while (i--) {
3648 				name = attributeFilters[i].name;
3649 
3650 				if (name in node.attributes.map) {
3651 					list = matchedAttributes[name];
3652 
3653 					if (list)
3654 						list.push(node);
3655 					else
3656 						matchedAttributes[name] = [node];
3657 				}
3658 			}
3659 
3660 			return node;
3661 		};
3662 
3663 		self.addNodeFilter = function(name, callback) {
3664 			tinymce.each(tinymce.explode(name), function(name) {
3665 				var list = nodeFilters[name];
3666 
3667 				if (!list)
3668 					nodeFilters[name] = list = [];
3669 
3670 				list.push(callback);
3671 			});
3672 		};
3673 
3674 		self.addAttributeFilter = function(name, callback) {
3675 			tinymce.each(tinymce.explode(name), function(name) {
3676 				var i;
3677 
3678 				for (i = 0; i < attributeFilters.length; i++) {
3679 					if (attributeFilters[i].name === name) {
3680 						attributeFilters[i].callbacks.push(callback);
3681 						return;
3682 					}
3683 				}
3684 
3685 				attributeFilters.push({name: name, callbacks: [callback]});
3686 			});
3687 		};
3688 
3689 		self.parse = function(html, args) {
3690 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3691 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3692 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3693 
3694 			args = args || {};
3695 			matchedNodes = {};
3696 			matchedAttributes = {};
3697 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3698 			nonEmptyElements = schema.getNonEmptyElements();
3699 			children = schema.children;
3700 			validate = settings.validate;
3701 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3702 
3703 			whiteSpaceElements = schema.getWhiteSpaceElements();
3704 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3705 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3706 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3707 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3708 
3709 			function addRootBlocks() {
3710 				var node = rootNode.firstChild, next, rootBlockNode;
3711 
3712 				while (node) {
3713 					next = node.next;
3714 
3715 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3716 						if (!rootBlockNode) {
3717 							// Create a new root block element
3718 							rootBlockNode = createNode(rootBlockName, 1);
3719 							rootNode.insert(rootBlockNode, node);
3720 							rootBlockNode.append(node);
3721 						} else
3722 							rootBlockNode.append(node);
3723 					} else {
3724 						rootBlockNode = null;
3725 					}
3726 
3727 					node = next;
3728 				};
3729 			};
3730 
3731 			function createNode(name, type) {
3732 				var node = new Node(name, type), list;
3733 
3734 				if (name in nodeFilters) {
3735 					list = matchedNodes[name];
3736 
3737 					if (list)
3738 						list.push(node);
3739 					else
3740 						matchedNodes[name] = [node];
3741 				}
3742 
3743 				return node;
3744 			};
3745 
3746 			function removeWhitespaceBefore(node) {
3747 				var textNode, textVal, sibling;
3748 
3749 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3750 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3751 
3752 					if (textVal.length > 0) {
3753 						textNode.value = textVal;
3754 						textNode = textNode.prev;
3755 					} else {
3756 						sibling = textNode.prev;
3757 						textNode.remove();
3758 						textNode = sibling;
3759 					}
3760 				}
3761 			};
3762 
3763 			function cloneAndExcludeBlocks(input) {
3764 				var name, output = {};
3765 
3766 				for (name in input) {
3767 					if (name !== 'li' && name != 'p') {
3768 						output[name] = input[name];
3769 					}
3770 				}
3771 
3772 				return output;
3773 			};
3774 
3775 			parser = new tinymce.html.SaxParser({
3776 				validate : validate,
3777 
3778 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3779 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3780 
3781 				cdata: function(text) {
3782 					node.append(createNode('#cdata', 4)).value = text;
3783 				},
3784 
3785 				text: function(text, raw) {
3786 					var textNode;
3787 
3788 					// Trim all redundant whitespace on non white space elements
3789 					if (!isInWhiteSpacePreservedElement) {
3790 						text = text.replace(allWhiteSpaceRegExp, ' ');
3791 
3792 						if (node.lastChild && blockElements[node.lastChild.name])
3793 							text = text.replace(startWhiteSpaceRegExp, '');
3794 					}
3795 
3796 					// Do we need to create the node
3797 					if (text.length !== 0) {
3798 						textNode = createNode('#text', 3);
3799 						textNode.raw = !!raw;
3800 						node.append(textNode).value = text;
3801 					}
3802 				},
3803 
3804 				comment: function(text) {
3805 					node.append(createNode('#comment', 8)).value = text;
3806 				},
3807 
3808 				pi: function(name, text) {
3809 					node.append(createNode(name, 7)).value = text;
3810 					removeWhitespaceBefore(node);
3811 				},
3812 
3813 				doctype: function(text) {
3814 					var newNode;
3815 		
3816 					newNode = node.append(createNode('#doctype', 10));
3817 					newNode.value = text;
3818 					removeWhitespaceBefore(node);
3819 				},
3820 
3821 				start: function(name, attrs, empty) {
3822 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3823 
3824 					elementRule = validate ? schema.getElementRule(name) : {};
3825 					if (elementRule) {
3826 						newNode = createNode(elementRule.outputName || name, 1);
3827 						newNode.attributes = attrs;
3828 						newNode.shortEnded = empty;
3829 
3830 						node.append(newNode);
3831 
3832 						// Check if node is valid child of the parent node is the child is
3833 						// unknown we don't collect it since it's probably a custom element
3834 						parent = children[node.name];
3835 						if (parent && children[newNode.name] && !parent[newNode.name])
3836 							invalidChildren.push(newNode);
3837 
3838 						attrFiltersLen = attributeFilters.length;
3839 						while (attrFiltersLen--) {
3840 							attrName = attributeFilters[attrFiltersLen].name;
3841 
3842 							if (attrName in attrs.map) {
3843 								list = matchedAttributes[attrName];
3844 
3845 								if (list)
3846 									list.push(newNode);
3847 								else
3848 									matchedAttributes[attrName] = [newNode];
3849 							}
3850 						}
3851 
3852 						// Trim whitespace before block
3853 						if (blockElements[name])
3854 							removeWhitespaceBefore(newNode);
3855 
3856 						// Change current node if the element wasn't empty i.e not <br /> or <img />
3857 						if (!empty)
3858 							node = newNode;
3859 
3860 						// Check if we are inside a whitespace preserved element
3861 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3862 							isInWhiteSpacePreservedElement = true;
3863 						}
3864 					}
3865 				},
3866 
3867 				end: function(name) {
3868 					var textNode, elementRule, text, sibling, tempNode;
3869 
3870 					elementRule = validate ? schema.getElementRule(name) : {};
3871 					if (elementRule) {
3872 						if (blockElements[name]) {
3873 							if (!isInWhiteSpacePreservedElement) {
3874 								// Trim whitespace of the first node in a block
3875 								textNode = node.firstChild;
3876 								if (textNode && textNode.type === 3) {
3877 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3878 
3879 									// Any characters left after trim or should we remove it
3880 									if (text.length > 0) {
3881 										textNode.value = text;
3882 										textNode = textNode.next;
3883 									} else {
3884 										sibling = textNode.next;
3885 										textNode.remove();
3886 										textNode = sibling;
3887 									}
3888 
3889 									// Remove any pure whitespace siblings
3890 									while (textNode && textNode.type === 3) {
3891 										text = textNode.value;
3892 										sibling = textNode.next;
3893 
3894 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3895 											textNode.remove();
3896 											textNode = sibling;
3897 										}
3898 
3899 										textNode = sibling;
3900 									}
3901 								}
3902 
3903 								// Trim whitespace of the last node in a block
3904 								textNode = node.lastChild;
3905 								if (textNode && textNode.type === 3) {
3906 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3907 
3908 									// Any characters left after trim or should we remove it
3909 									if (text.length > 0) {
3910 										textNode.value = text;
3911 										textNode = textNode.prev;
3912 									} else {
3913 										sibling = textNode.prev;
3914 										textNode.remove();
3915 										textNode = sibling;
3916 									}
3917 
3918 									// Remove any pure whitespace siblings
3919 									while (textNode && textNode.type === 3) {
3920 										text = textNode.value;
3921 										sibling = textNode.prev;
3922 
3923 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
3924 											textNode.remove();
3925 											textNode = sibling;
3926 										}
3927 
3928 										textNode = sibling;
3929 									}
3930 								}
3931 							}
3932 
3933 							// Trim start white space
3934 							textNode = node.prev;
3935 							if (textNode && textNode.type === 3) {
3936 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3937 
3938 								if (text.length > 0)
3939 									textNode.value = text;
3940 								else
3941 									textNode.remove();
3942 							}
3943 						}
3944 
3945 						// Check if we exited a whitespace preserved element
3946 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
3947 							isInWhiteSpacePreservedElement = false;
3948 						}
3949 
3950 						// Handle empty nodes
3951 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3952 							if (node.isEmpty(nonEmptyElements)) {
3953 								if (elementRule.paddEmpty)
3954 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3955 								else {
3956 									// Leave nodes that have a name like <a name="name">
3957 									if (!node.attributes.map.name && !node.attributes.map.id) {
3958 										tempNode = node.parent;
3959 										node.empty().remove();
3960 										node = tempNode;
3961 										return;
3962 									}
3963 								}
3964 							}
3965 						}
3966 
3967 						node = node.parent;
3968 					}
3969 				}
3970 			}, schema);
3971 
3972 			rootNode = node = new Node(args.context || settings.root_name, 11);
3973 
3974 			parser.parse(html);
3975 
3976 			// Fix invalid children or report invalid children in a contextual parsing
3977 			if (validate && invalidChildren.length) {
3978 				if (!args.context)
3979 					fixInvalidChildren(invalidChildren);
3980 				else
3981 					args.invalid = true;
3982 			}
3983 
3984 			// Wrap nodes in the root into block elements if the root is body
3985 			if (rootBlockName && rootNode.name == 'body')
3986 				addRootBlocks();
3987 
3988 			// Run filters only when the contents is valid
3989 			if (!args.invalid) {
3990 				// Run node filters
3991 				for (name in matchedNodes) {
3992 					list = nodeFilters[name];
3993 					nodes = matchedNodes[name];
3994 
3995 					// Remove already removed children
3996 					fi = nodes.length;
3997 					while (fi--) {
3998 						if (!nodes[fi].parent)
3999 							nodes.splice(fi, 1);
4000 					}
4001 
4002 					for (i = 0, l = list.length; i < l; i++)
4003 						list[i](nodes, name, args);
4004 				}
4005 
4006 				// Run attribute filters
4007 				for (i = 0, l = attributeFilters.length; i < l; i++) {
4008 					list = attributeFilters[i];
4009 
4010 					if (list.name in matchedAttributes) {
4011 						nodes = matchedAttributes[list.name];
4012 
4013 						// Remove already removed children
4014 						fi = nodes.length;
4015 						while (fi--) {
4016 							if (!nodes[fi].parent)
4017 								nodes.splice(fi, 1);
4018 						}
4019 
4020 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4021 							list.callbacks[fi](nodes, list.name, args);
4022 					}
4023 				}
4024 			}
4025 
4026 			return rootNode;
4027 		};
4028 
4029 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4030 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4031 		// these elements and keep br elements that where intended to be there intact
4032 		if (settings.remove_trailing_brs) {
4033 			self.addNodeFilter('br', function(nodes, name) {
4034 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4035 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4036 
4037 				// Remove brs from body element as well
4038 				blockElements.body = 1;
4039 
4040 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4041 				for (i = 0; i < l; i++) {
4042 					node = nodes[i];
4043 					parent = node.parent;
4044 
4045 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4046 						// Loop all nodes to the left of the current node and check for other BR elements
4047 						// excluding bookmarks since they are invisible
4048 						prev = node.prev;
4049 						while (prev) {
4050 							prevName = prev.name;
4051 
4052 							// Ignore bookmarks
4053 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4054 								// Found a non BR element
4055 								if (prevName !== "br")
4056 									break;
4057 	
4058 								// Found another br it's a <br><br> structure then don't remove anything
4059 								if (prevName === 'br') {
4060 									node = null;
4061 									break;
4062 								}
4063 							}
4064 
4065 							prev = prev.prev;
4066 						}
4067 
4068 						if (node) {
4069 							node.remove();
4070 
4071 							// Is the parent to be considered empty after we removed the BR
4072 							if (parent.isEmpty(nonEmptyElements)) {
4073 								elementRule = schema.getElementRule(parent.name);
4074 
4075 								// Remove or padd the element depending on schema rule
4076 								if (elementRule) {
4077 									if (elementRule.removeEmpty)
4078 										parent.remove();
4079 									else if (elementRule.paddEmpty)
4080 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4081 								}
4082 							}
4083 						}
4084 					} else {
4085 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4086 						lastParent = node;
4087 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4088 							lastParent = parent;
4089 
4090 							if (blockElements[parent.name]) {
4091 								break;
4092 							}
4093 
4094 							parent = parent.parent;
4095 						}
4096 
4097 						if (lastParent === parent) {
4098 							textNode = new tinymce.html.Node('#text', 3);
4099 							textNode.value = '\u00a0';
4100 							node.replace(textNode);
4101 						}
4102 					}
4103 				}
4104 			});
4105 		}
4106 
4107 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4108 		if (!settings.allow_html_in_named_anchor) {
4109 			self.addAttributeFilter('id,name', function(nodes, name) {
4110 				var i = nodes.length, sibling, prevSibling, parent, node;
4111 
4112 				while (i--) {
4113 					node = nodes[i];
4114 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4115 						parent = node.parent;
4116 
4117 						// Move children after current node
4118 						sibling = node.lastChild;
4119 						do {
4120 							prevSibling = sibling.prev;
4121 							parent.insert(sibling, node);
4122 							sibling = prevSibling;
4123 						} while (sibling);
4124 					}
4125 				}
4126 			});
4127 		}
4128 	}
4129 })(tinymce);
4130 
4131 tinymce.html.Writer = function(settings) {
4132 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4133 
4134 	settings = settings || {};
4135 	indent = settings.indent;
4136 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4137 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4138 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4139 	htmlOutput = settings.element_format == "html";
4140 
4141 	return {
4142 		start: function(name, attrs, empty) {
4143 			var i, l, attr, value;
4144 
4145 			if (indent && indentBefore[name] && html.length > 0) {
4146 				value = html[html.length - 1];
4147 
4148 				if (value.length > 0 && value !== '\n')
4149 					html.push('\n');
4150 			}
4151 
4152 			html.push('<', name);
4153 
4154 			if (attrs) {
4155 				for (i = 0, l = attrs.length; i < l; i++) {
4156 					attr = attrs[i];
4157 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4158 				}
4159 			}
4160 
4161 			if (!empty || htmlOutput)
4162 				html[html.length] = '>';
4163 			else
4164 				html[html.length] = ' />';
4165 
4166 			if (empty && indent && indentAfter[name] && html.length > 0) {
4167 				value = html[html.length - 1];
4168 
4169 				if (value.length > 0 && value !== '\n')
4170 					html.push('\n');
4171 			}
4172 		},
4173 
4174 		end: function(name) {
4175 			var value;
4176 
4177 			/*if (indent && indentBefore[name] && html.length > 0) {
4178 				value = html[html.length - 1];
4179 
4180 				if (value.length > 0 && value !== '\n')
4181 					html.push('\n');
4182 			}*/
4183 
4184 			html.push('</', name, '>');
4185 
4186 			if (indent && indentAfter[name] && html.length > 0) {
4187 				value = html[html.length - 1];
4188 
4189 				if (value.length > 0 && value !== '\n')
4190 					html.push('\n');
4191 			}
4192 		},
4193 
4194 		text: function(text, raw) {
4195 			if (text.length > 0)
4196 				html[html.length] = raw ? text : encode(text);
4197 		},
4198 
4199 		cdata: function(text) {
4200 			html.push('<![CDATA[', text, ']]>');
4201 		},
4202 
4203 		comment: function(text) {
4204 			html.push('<!--', text, '-->');
4205 		},
4206 
4207 		pi: function(name, text) {
4208 			if (text)
4209 				html.push('<?', name, ' ', text, '?>');
4210 			else
4211 				html.push('<?', name, '?>');
4212 
4213 			if (indent)
4214 				html.push('\n');
4215 		},
4216 
4217 		doctype: function(text) {
4218 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4219 		},
4220 
4221 		reset: function() {
4222 			html.length = 0;
4223 		},
4224 
4225 		getContent: function() {
4226 			return html.join('').replace(/\n$/, '');
4227 		}
4228 	};
4229 };
4230 
4231 (function(tinymce) {
4232 	tinymce.html.Serializer = function(settings, schema) {
4233 		var self = this, writer = new tinymce.html.Writer(settings);
4234 
4235 		settings = settings || {};
4236 		settings.validate = "validate" in settings ? settings.validate : true;
4237 
4238 		self.schema = schema = schema || new tinymce.html.Schema();
4239 		self.writer = writer;
4240 
4241 		self.serialize = function(node) {
4242 			var handlers, validate;
4243 
4244 			validate = settings.validate;
4245 
4246 			handlers = {
4247 				// #text
4248 				3: function(node, raw) {
4249 					writer.text(node.value, node.raw);
4250 				},
4251 
4252 				// #comment
4253 				8: function(node) {
4254 					writer.comment(node.value);
4255 				},
4256 
4257 				// Processing instruction
4258 				7: function(node) {
4259 					writer.pi(node.name, node.value);
4260 				},
4261 
4262 				// Doctype
4263 				10: function(node) {
4264 					writer.doctype(node.value);
4265 				},
4266 
4267 				// CDATA
4268 				4: function(node) {
4269 					writer.cdata(node.value);
4270 				},
4271 
4272 				// Document fragment
4273 				11: function(node) {
4274 					if ((node = node.firstChild)) {
4275 						do {
4276 							walk(node);
4277 						} while (node = node.next);
4278 					}
4279 				}
4280 			};
4281 
4282 			writer.reset();
4283 
4284 			function walk(node) {
4285 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4286 
4287 				if (!handler) {
4288 					name = node.name;
4289 					isEmpty = node.shortEnded;
4290 					attrs = node.attributes;
4291 
4292 					// Sort attributes
4293 					if (validate && attrs && attrs.length > 1) {
4294 						sortedAttrs = [];
4295 						sortedAttrs.map = {};
4296 
4297 						elementRule = schema.getElementRule(node.name);
4298 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4299 							attrName = elementRule.attributesOrder[i];
4300 
4301 							if (attrName in attrs.map) {
4302 								attrValue = attrs.map[attrName];
4303 								sortedAttrs.map[attrName] = attrValue;
4304 								sortedAttrs.push({name: attrName, value: attrValue});
4305 							}
4306 						}
4307 
4308 						for (i = 0, l = attrs.length; i < l; i++) {
4309 							attrName = attrs[i].name;
4310 
4311 							if (!(attrName in sortedAttrs.map)) {
4312 								attrValue = attrs.map[attrName];
4313 								sortedAttrs.map[attrName] = attrValue;
4314 								sortedAttrs.push({name: attrName, value: attrValue});
4315 							}
4316 						}
4317 
4318 						attrs = sortedAttrs;
4319 					}
4320 
4321 					writer.start(node.name, attrs, isEmpty);
4322 
4323 					if (!isEmpty) {
4324 						if ((node = node.firstChild)) {
4325 							do {
4326 								walk(node);
4327 							} while (node = node.next);
4328 						}
4329 
4330 						writer.end(name);
4331 					}
4332 				} else
4333 					handler(node);
4334 			}
4335 
4336 			// Serialize element and treat all non elements as fragments
4337 			if (node.type == 1 && !settings.inner)
4338 				walk(node);
4339 			else
4340 				handlers[11](node);
4341 
4342 			return writer.getContent();
4343 		};
4344 	}
4345 })(tinymce);
4346 
4347 // JSLint defined globals
4348 /*global tinymce:false, window:false */
4349 
4350 tinymce.dom = {};
4351 
4352 (function(namespace, expando) {
4353 	var w3cEventModel = !!document.addEventListener;
4354 
4355 	function addEvent(target, name, callback, capture) {
4356 		if (target.addEventListener) {
4357 			target.addEventListener(name, callback, capture || false);
4358 		} else if (target.attachEvent) {
4359 			target.attachEvent('on' + name, callback);
4360 		}
4361 	}
4362 
4363 	function removeEvent(target, name, callback, capture) {
4364 		if (target.removeEventListener) {
4365 			target.removeEventListener(name, callback, capture || false);
4366 		} else if (target.detachEvent) {
4367 			target.detachEvent('on' + name, callback);
4368 		}
4369 	}
4370 
4371 	function fix(original_event, data) {
4372 		var name, event = data || {};
4373 
4374 		// Dummy function that gets replaced on the delegation state functions
4375 		function returnFalse() {
4376 			return false;
4377 		}
4378 
4379 		// Dummy function that gets replaced on the delegation state functions
4380 		function returnTrue() {
4381 			return true;
4382 		}
4383 
4384 		// Copy all properties from the original event
4385 		for (name in original_event) {
4386 			// layerX/layerY is deprecated in Chrome and produces a warning
4387 			if (name !== "layerX" && name !== "layerY") {
4388 				event[name] = original_event[name];
4389 			}
4390 		}
4391 
4392 		// Normalize target IE uses srcElement
4393 		if (!event.target) {
4394 			event.target = event.srcElement || document;
4395 		}
4396 
4397 		// Add preventDefault method
4398 		event.preventDefault = function() {
4399 			event.isDefaultPrevented = returnTrue;
4400 
4401 			// Execute preventDefault on the original event object
4402 			if (original_event) {
4403 				if (original_event.preventDefault) {
4404 					original_event.preventDefault();
4405 				} else {
4406 					original_event.returnValue = false; // IE
4407 				}
4408 			}
4409 		};
4410 
4411 		// Add stopPropagation
4412 		event.stopPropagation = function() {
4413 			event.isPropagationStopped = returnTrue;
4414 
4415 			// Execute stopPropagation on the original event object
4416 			if (original_event) {
4417 				if (original_event.stopPropagation) {
4418 					original_event.stopPropagation();
4419 				} else {
4420 					original_event.cancelBubble = true; // IE
4421 				}
4422 			 }
4423 		};
4424 
4425 		// Add stopImmediatePropagation
4426 		event.stopImmediatePropagation = function() {
4427 			event.isImmediatePropagationStopped = returnTrue;
4428 			event.stopPropagation();
4429 		};
4430 
4431 		// Add event delegation states
4432 		if (!event.isDefaultPrevented) {
4433 			event.isDefaultPrevented = returnFalse;
4434 			event.isPropagationStopped = returnFalse;
4435 			event.isImmediatePropagationStopped = returnFalse;
4436 		}
4437 
4438 		return event;
4439 	}
4440 
4441 	function bindOnReady(win, callback, event_utils) {
4442 		var doc = win.document, event = {type: 'ready'};
4443 
4444 		// Gets called when the DOM is ready
4445 		function readyHandler() {
4446 			if (!event_utils.domLoaded) {
4447 				event_utils.domLoaded = true;
4448 				callback(event);
4449 			}
4450 		}
4451 
4452 		// Use W3C method
4453 		if (w3cEventModel) {
4454 			addEvent(win, 'DOMContentLoaded', readyHandler);
4455 		} else {
4456 			// Use IE method
4457 			addEvent(doc, "readystatechange", function() {
4458 				if (doc.readyState === "complete") {
4459 					removeEvent(doc, "readystatechange", arguments.callee);
4460 					readyHandler();
4461 				}
4462 			});
4463 
4464 			// Wait until we can scroll, when we can the DOM is initialized
4465 			if (doc.documentElement.doScroll && win === win.top) {
4466 				(function() {
4467 					try {
4468 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4469 						// http://javascript.nwbox.com/IEContentLoaded/
4470 						doc.documentElement.doScroll("left");
4471 					} catch (ex) {
4472 						setTimeout(arguments.callee, 0);
4473 						return;
4474 					}
4475 
4476 					readyHandler();
4477 				})();
4478 			}
4479 		}
4480 
4481 		// Fallback if any of the above methods should fail for some odd reason
4482 		addEvent(win, 'load', readyHandler);
4483 	}
4484 
4485 	function EventUtils(proxy) {
4486 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4487 
4488 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4489 		hasFocusIn = "onfocusin" in document.documentElement;
4490 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4491 		count = 1;
4492 
4493 		// State if the DOMContentLoaded was executed or not
4494 		self.domLoaded = false;
4495 		self.events = events;
4496 
4497 		function executeHandlers(evt, id) {
4498 			var callbackList, i, l, callback;
4499 
4500 			callbackList = events[id][evt.type];
4501 			if (callbackList) {
4502 				for (i = 0, l = callbackList.length; i < l; i++) {
4503 					callback = callbackList[i];
4504 					
4505 					// Check if callback exists might be removed if a unbind is called inside the callback
4506 					if (callback && callback.func.call(callback.scope, evt) === false) {
4507 						evt.preventDefault();
4508 					}
4509 
4510 					// Should we stop propagation to immediate listeners
4511 					if (evt.isImmediatePropagationStopped()) {
4512 						return;
4513 					}
4514 				}
4515 			}
4516 		}
4517 
4518 		self.bind = function(target, names, callback, scope) {
4519 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4520 
4521 			// Native event handler function patches the event and executes the callbacks for the expando
4522 			function defaultNativeHandler(evt) {
4523 				executeHandlers(fix(evt || win.event), id);
4524 			}
4525 
4526 			// Don't bind to text nodes or comments
4527 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4528 				return;
4529 			}
4530 
4531 			// Create or get events id for the target
4532 			if (!target[expando]) {
4533 				id = count++;
4534 				target[expando] = id;
4535 				events[id] = {};
4536 			} else {
4537 				id = target[expando];
4538 
4539 				if (!events[id]) {
4540 					events[id] = {};
4541 				}
4542 			}
4543 
4544 			// Setup the specified scope or use the target as a default
4545 			scope = scope || target;
4546 
4547 			// Split names and bind each event, enables you to bind multiple events with one call
4548 			names = names.split(' ');
4549 			i = names.length;
4550 			while (i--) {
4551 				name = names[i];
4552 				nativeHandler = defaultNativeHandler;
4553 				fakeName = capture = false;
4554 
4555 				// Use ready instead of DOMContentLoaded
4556 				if (name === "DOMContentLoaded") {
4557 					name = "ready";
4558 				}
4559 
4560 				// DOM is already ready
4561 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4562 					self.domLoaded = true;
4563 					callback.call(scope, fix({type: name}));
4564 					continue;
4565 				}
4566 
4567 				// Handle mouseenter/mouseleaver
4568 				if (!hasMouseEnterLeave) {
4569 					fakeName = mouseEnterLeave[name];
4570 
4571 					if (fakeName) {
4572 						nativeHandler = function(evt) {
4573 							var current, related;
4574 
4575 							current = evt.currentTarget;
4576 							related = evt.relatedTarget;
4577 
4578 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4579 							if (related && current.contains) {
4580 								// Use contains for performance
4581 								related = current.contains(related);
4582 							} else {
4583 								while (related && related !== current) {
4584 									related = related.parentNode;
4585 								}
4586 							}
4587 
4588 							// Fire fake event
4589 							if (!related) {
4590 								evt = fix(evt || win.event);
4591 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4592 								evt.target = current;
4593 								executeHandlers(evt, id);
4594 							}
4595 						};
4596 					}
4597 				}
4598 
4599 				// Fake bubbeling of focusin/focusout
4600 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4601 					capture = true;
4602 					fakeName = name === "focusin" ? "focus" : "blur";
4603 					nativeHandler = function(evt) {
4604 						evt = fix(evt || win.event);
4605 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4606 						executeHandlers(evt, id);
4607 					};
4608 				}
4609 
4610 				// Setup callback list and bind native event
4611 				callbackList = events[id][name];
4612 				if (!callbackList) {
4613 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4614 					callbackList.fakeName = fakeName;
4615 					callbackList.capture = capture;
4616 
4617 					// Add the nativeHandler to the callback list so that we can later unbind it
4618 					callbackList.nativeHandler = nativeHandler;
4619 					if (!w3cEventModel) {
4620 						callbackList.proxyHandler = proxy(id);
4621 					}
4622 
4623 					// Check if the target has native events support
4624 					if (name === "ready") {
4625 						bindOnReady(target, nativeHandler, self);
4626 					} else {
4627 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4628 					}
4629 				} else {
4630 					// If it already has an native handler then just push the callback
4631 					callbackList.push({func: callback, scope: scope});
4632 				}
4633 			}
4634 
4635 			target = callbackList = 0; // Clean memory for IE
4636 
4637 			return callback;
4638 		};
4639 
4640 		self.unbind = function(target, names, callback) {
4641 			var id, callbackList, i, ci, name, eventMap;
4642 
4643 			// Don't bind to text nodes or comments
4644 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4645 				return self;
4646 			}
4647 
4648 			// Unbind event or events if the target has the expando
4649 			id = target[expando];
4650 			if (id) {
4651 				eventMap = events[id];
4652 
4653 				// Specific callback
4654 				if (names) {
4655 					names = names.split(' ');
4656 					i = names.length;
4657 					while (i--) {
4658 						name = names[i];
4659 						callbackList = eventMap[name];
4660 
4661 						// Unbind the event if it exists in the map
4662 						if (callbackList) {
4663 							// Remove specified callback
4664 							if (callback) {
4665 								ci = callbackList.length;
4666 								while (ci--) {
4667 									if (callbackList[ci].func === callback) {
4668 										callbackList.splice(ci, 1);
4669 									}
4670 								}
4671 							}
4672 
4673 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4674 							if (!callback || callbackList.length === 0) {
4675 								delete eventMap[name];
4676 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4677 							}
4678 						}
4679 					}
4680 				} else {
4681 					// All events for a specific element
4682 					for (name in eventMap) {
4683 						callbackList = eventMap[name];
4684 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4685 					}
4686 
4687 					eventMap = {};
4688 				}
4689 
4690 				// Check if object is empty, if it isn't then we won't remove the expando map
4691 				for (name in eventMap) {
4692 					return self;
4693 				}
4694 
4695 				// Delete event object
4696 				delete events[id];
4697 
4698 				// Remove expando from target
4699 				try {
4700 					// IE will fail here since it can't delete properties from window
4701 					delete target[expando];
4702 				} catch (ex) {
4703 					// IE will set it to null
4704 					target[expando] = null;
4705 				}
4706 			}
4707 
4708 			return self;
4709 		};
4710 
4711 		self.fire = function(target, name, args) {
4712 			var id, event;
4713 
4714 			// Don't bind to text nodes or comments
4715 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4716 				return self;
4717 			}
4718 
4719 			// Build event object by patching the args
4720 			event = fix(null, args);
4721 			event.type = name;
4722 
4723 			do {
4724 				// Found an expando that means there is listeners to execute
4725 				id = target[expando];
4726 				if (id) {
4727 					executeHandlers(event, id);
4728 				}
4729 
4730 				// Walk up the DOM
4731 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4732 			} while (target && !event.isPropagationStopped());
4733 
4734 			return self;
4735 		};
4736 
4737 		self.clean = function(target) {
4738 			var i, children, unbind = self.unbind;
4739 	
4740 			// Don't bind to text nodes or comments
4741 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4742 				return self;
4743 			}
4744 
4745 			// Unbind any element on the specificed target
4746 			if (target[expando]) {
4747 				unbind(target);
4748 			}
4749 
4750 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4751 			if (!target.getElementsByTagName) {
4752 				target = target.document;
4753 			}
4754 
4755 			// Remove events from each child element
4756 			if (target && target.getElementsByTagName) {
4757 				unbind(target);
4758 
4759 				children = target.getElementsByTagName('*');
4760 				i = children.length;
4761 				while (i--) {
4762 					target = children[i];
4763 
4764 					if (target[expando]) {
4765 						unbind(target);
4766 					}
4767 				}
4768 			}
4769 
4770 			return self;
4771 		};
4772 
4773 		self.callNativeHandler = function(id, evt) {
4774 			if (events) {
4775 				events[id][evt.type].nativeHandler(evt);
4776 			}
4777 		};
4778 
4779 		self.destory = function() {
4780 			events = {};
4781 		};
4782 
4783 		// Legacy function calls
4784 
4785 		self.add = function(target, events, func, scope) {
4786 			// Old API supported direct ID assignment
4787 			if (typeof(target) === "string") {
4788 				target = document.getElementById(target);
4789 			}
4790 
4791 			// Old API supported multiple targets
4792 			if (target && target instanceof Array) {
4793 				var i = target.length;
4794 
4795 				while (i--) {
4796 					self.add(target[i], events, func, scope);
4797 				}
4798 
4799 				return;
4800 			}
4801 
4802 			// Old API called ready init
4803 			if (events === "init") {
4804 				events = "ready";
4805 			}
4806 
4807 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
4808 		};
4809 
4810 		self.remove = function(target, events, func, scope) {
4811 			if (!target) {
4812 				return self;
4813 			}
4814 
4815 			// Old API supported direct ID assignment
4816 			if (typeof(target) === "string") {
4817 				target = document.getElementById(target);
4818 			}
4819 
4820 			// Old API supported multiple targets
4821 			if (target instanceof Array) {
4822 				var i = target.length;
4823 
4824 				while (i--) {
4825 					self.remove(target[i], events, func, scope);
4826 				}
4827 
4828 				return self;
4829 			}
4830 
4831 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
4832 		};
4833 
4834 		self.clear = function(target) {
4835 			// Old API supported direct ID assignment
4836 			if (typeof(target) === "string") {
4837 				target = document.getElementById(target);
4838 			}
4839 
4840 			return self.clean(target);
4841 		};
4842 
4843 		self.cancel = function(e) {
4844 			if (e) {
4845 				self.prevent(e);
4846 				self.stop(e);
4847 			}
4848 
4849 			return false;
4850 		};
4851 
4852 		self.prevent = function(e) {
4853 			if (!e.preventDefault) {
4854 				e = fix(e);
4855 			}
4856 
4857 			e.preventDefault();
4858 
4859 			return false;
4860 		};
4861 
4862 		self.stop = function(e) {
4863 			if (!e.stopPropagation) {
4864 				e = fix(e);
4865 			}
4866 
4867 			e.stopPropagation();
4868 
4869 			return false;
4870 		};
4871 	}
4872 
4873 	namespace.EventUtils = EventUtils;
4874 
4875 	namespace.Event = new EventUtils(function(id) {
4876 		return function(evt) {
4877 			tinymce.dom.Event.callNativeHandler(id, evt);
4878 		};
4879 	});
4880 
4881 	// Bind ready event when tinymce script is loaded
4882 	namespace.Event.bind(window, 'ready', function() {});
4883 
4884 	namespace = 0;
4885 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
4886 
4887 tinymce.dom.TreeWalker = function(start_node, root_node) {
4888 	var node = start_node;
4889 
4890 	function findSibling(node, start_name, sibling_name, shallow) {
4891 		var sibling, parent;
4892 
4893 		if (node) {
4894 			// Walk into nodes if it has a start
4895 			if (!shallow && node[start_name])
4896 				return node[start_name];
4897 
4898 			// Return the sibling if it has one
4899 			if (node != root_node) {
4900 				sibling = node[sibling_name];
4901 				if (sibling)
4902 					return sibling;
4903 
4904 				// Walk up the parents to look for siblings
4905 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
4906 					sibling = parent[sibling_name];
4907 					if (sibling)
4908 						return sibling;
4909 				}
4910 			}
4911 		}
4912 	};
4913 
4914 	this.current = function() {
4915 		return node;
4916 	};
4917 
4918 	this.next = function(shallow) {
4919 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
4920 	};
4921 
4922 	this.prev = function(shallow) {
4923 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
4924 	};
4925 };
4926 
4927 (function(tinymce) {
4928 	// Shorten names
4929 	var each = tinymce.each,
4930 		is = tinymce.is,
4931 		isWebKit = tinymce.isWebKit,
4932 		isIE = tinymce.isIE,
4933 		Entities = tinymce.html.Entities,
4934 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
4935 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
4936 
4937 	tinymce.create('tinymce.dom.DOMUtils', {
4938 		doc : null,
4939 		root : null,
4940 		files : null,
4941 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
4942 		props : {
4943 			"for" : "htmlFor",
4944 			"class" : "className",
4945 			className : "className",
4946 			checked : "checked",
4947 			disabled : "disabled",
4948 			maxlength : "maxLength",
4949 			readonly : "readOnly",
4950 			selected : "selected",
4951 			value : "value",
4952 			id : "id",
4953 			name : "name",
4954 			type : "type"
4955 		},
4956 
4957 		DOMUtils : function(d, s) {
4958 			var t = this, globalStyle, name, blockElementsMap;
4959 
4960 			t.doc = d;
4961 			t.win = window;
4962 			t.files = {};
4963 			t.cssFlicker = false;
4964 			t.counter = 0;
4965 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
4966 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
4967 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
4968 
4969 			t.settings = s = tinymce.extend({
4970 				keep_values : false,
4971 				hex_colors : 1
4972 			}, s);
4973 			
4974 			t.schema = s.schema;
4975 			t.styles = new tinymce.html.Styles({
4976 				url_converter : s.url_converter,
4977 				url_converter_scope : s.url_converter_scope
4978 			}, s.schema);
4979 
4980 			// Fix IE6SP2 flicker and check it failed for pre SP2
4981 			if (tinymce.isIE6) {
4982 				try {
4983 					d.execCommand('BackgroundImageCache', false, true);
4984 				} catch (e) {
4985 					t.cssFlicker = true;
4986 				}
4987 			}
4988 
4989 			t.fixDoc(d);
4990 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
4991 			tinymce.addUnload(t.destroy, t);
4992 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
4993 
4994 			t.isBlock = function(node) {
4995 				// This function is called in module pattern style since it might be executed with the wrong this scope
4996 				var type = node.nodeType;
4997 
4998 				// If it's a node then check the type and use the nodeName
4999 				if (type)
5000 					return !!(type === 1 && blockElementsMap[node.nodeName]);
5001 
5002 				return !!blockElementsMap[node];
5003 			};
5004 		},
5005 
5006 		fixDoc: function(doc) {
5007 			var settings = this.settings, name;
5008 
5009 			if (isIE && settings.schema) {
5010 				// Add missing HTML 4/5 elements to IE
5011 				('abbr article aside audio canvas ' +
5012 				'details figcaption figure footer ' +
5013 				'header hgroup mark menu meter nav ' +
5014 				'output progress section summary ' +
5015 				'time video').replace(/\w+/g, function(name) {
5016 					doc.createElement(name);
5017 				});
5018 
5019 				// Create all custom elements
5020 				for (name in settings.schema.getCustomElements()) {
5021 					doc.createElement(name);
5022 				}
5023 			}
5024 		},
5025 
5026 		clone: function(node, deep) {
5027 			var self = this, clone, doc;
5028 
5029 			// TODO: Add feature detection here in the future
5030 			if (!isIE || node.nodeType !== 1 || deep) {
5031 				return node.cloneNode(deep);
5032 			}
5033 
5034 			doc = self.doc;
5035 
5036 			// Make a HTML5 safe shallow copy
5037 			if (!deep) {
5038 				clone = doc.createElement(node.nodeName);
5039 
5040 				// Copy attribs
5041 				each(self.getAttribs(node), function(attr) {
5042 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5043 				});
5044 
5045 				return clone;
5046 			}
5047 /*
5048 			// Setup HTML5 patched document fragment
5049 			if (!self.frag) {
5050 				self.frag = doc.createDocumentFragment();
5051 				self.fixDoc(self.frag);
5052 			}
5053 
5054 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5055 			clone = doc.createElement('div');
5056 			self.frag.appendChild(clone);
5057 			clone.innerHTML = node.outerHTML;
5058 			self.frag.removeChild(clone);
5059 */
5060 			return clone.firstChild;
5061 		},
5062 
5063 		getRoot : function() {
5064 			var t = this, s = t.settings;
5065 
5066 			return (s && t.get(s.root_element)) || t.doc.body;
5067 		},
5068 
5069 		getViewPort : function(w) {
5070 			var d, b;
5071 
5072 			w = !w ? this.win : w;
5073 			d = w.document;
5074 			b = this.boxModel ? d.documentElement : d.body;
5075 
5076 			// Returns viewport size excluding scrollbars
5077 			return {
5078 				x : w.pageXOffset || b.scrollLeft,
5079 				y : w.pageYOffset || b.scrollTop,
5080 				w : w.innerWidth || b.clientWidth,
5081 				h : w.innerHeight || b.clientHeight
5082 			};
5083 		},
5084 
5085 		getRect : function(e) {
5086 			var p, t = this, sr;
5087 
5088 			e = t.get(e);
5089 			p = t.getPos(e);
5090 			sr = t.getSize(e);
5091 
5092 			return {
5093 				x : p.x,
5094 				y : p.y,
5095 				w : sr.w,
5096 				h : sr.h
5097 			};
5098 		},
5099 
5100 		getSize : function(e) {
5101 			var t = this, w, h;
5102 
5103 			e = t.get(e);
5104 			w = t.getStyle(e, 'width');
5105 			h = t.getStyle(e, 'height');
5106 
5107 			// Non pixel value, then force offset/clientWidth
5108 			if (w.indexOf('px') === -1)
5109 				w = 0;
5110 
5111 			// Non pixel value, then force offset/clientWidth
5112 			if (h.indexOf('px') === -1)
5113 				h = 0;
5114 
5115 			return {
5116 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5117 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5118 			};
5119 		},
5120 
5121 		getParent : function(n, f, r) {
5122 			return this.getParents(n, f, r, false);
5123 		},
5124 
5125 		getParents : function(n, f, r, c) {
5126 			var t = this, na, se = t.settings, o = [];
5127 
5128 			n = t.get(n);
5129 			c = c === undefined;
5130 
5131 			if (se.strict_root)
5132 				r = r || t.getRoot();
5133 
5134 			// Wrap node name as func
5135 			if (is(f, 'string')) {
5136 				na = f;
5137 
5138 				if (f === '*') {
5139 					f = function(n) {return n.nodeType == 1;};
5140 				} else {
5141 					f = function(n) {
5142 						return t.is(n, na);
5143 					};
5144 				}
5145 			}
5146 
5147 			while (n) {
5148 				if (n == r || !n.nodeType || n.nodeType === 9)
5149 					break;
5150 
5151 				if (!f || f(n)) {
5152 					if (c)
5153 						o.push(n);
5154 					else
5155 						return n;
5156 				}
5157 
5158 				n = n.parentNode;
5159 			}
5160 
5161 			return c ? o : null;
5162 		},
5163 
5164 		get : function(e) {
5165 			var n;
5166 
5167 			if (e && this.doc && typeof(e) == 'string') {
5168 				n = e;
5169 				e = this.doc.getElementById(e);
5170 
5171 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5172 				if (e && e.id !== n)
5173 					return this.doc.getElementsByName(n)[1];
5174 			}
5175 
5176 			return e;
5177 		},
5178 
5179 		getNext : function(node, selector) {
5180 			return this._findSib(node, selector, 'nextSibling');
5181 		},
5182 
5183 		getPrev : function(node, selector) {
5184 			return this._findSib(node, selector, 'previousSibling');
5185 		},
5186 
5187 
5188 		select : function(pa, s) {
5189 			var t = this;
5190 
5191 			return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5192 		},
5193 
5194 		is : function(n, selector) {
5195 			var i;
5196 
5197 			// If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5198 			if (n.length === undefined) {
5199 				// Simple all selector
5200 				if (selector === '*')
5201 					return n.nodeType == 1;
5202 
5203 				// Simple selector just elements
5204 				if (simpleSelectorRe.test(selector)) {
5205 					selector = selector.toLowerCase().split(/,/);
5206 					n = n.nodeName.toLowerCase();
5207 
5208 					for (i = selector.length - 1; i >= 0; i--) {
5209 						if (selector[i] == n)
5210 							return true;
5211 					}
5212 
5213 					return false;
5214 				}
5215 			}
5216 
5217 			return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5218 		},
5219 
5220 
5221 		add : function(p, n, a, h, c) {
5222 			var t = this;
5223 
5224 			return this.run(p, function(p) {
5225 				var e, k;
5226 
5227 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5228 				t.setAttribs(e, a);
5229 
5230 				if (h) {
5231 					if (h.nodeType)
5232 						e.appendChild(h);
5233 					else
5234 						t.setHTML(e, h);
5235 				}
5236 
5237 				return !c ? p.appendChild(e) : e;
5238 			});
5239 		},
5240 
5241 		create : function(n, a, h) {
5242 			return this.add(this.doc.createElement(n), n, a, h, 1);
5243 		},
5244 
5245 		createHTML : function(n, a, h) {
5246 			var o = '', t = this, k;
5247 
5248 			o += '<' + n;
5249 
5250 			for (k in a) {
5251 				if (a.hasOwnProperty(k))
5252 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5253 			}
5254 
5255 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5256 			if (typeof(h) != "undefined")
5257 				return o + '>' + h + '</' + n + '>';
5258 
5259 			return o + ' />';
5260 		},
5261 
5262 		remove : function(node, keep_children) {
5263 			return this.run(node, function(node) {
5264 				var child, parent = node.parentNode;
5265 
5266 				if (!parent)
5267 					return null;
5268 
5269 				if (keep_children) {
5270 					while (child = node.firstChild) {
5271 						// IE 8 will crash if you don't remove completely empty text nodes
5272 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5273 							parent.insertBefore(child, node);
5274 						else
5275 							node.removeChild(child);
5276 					}
5277 				}
5278 
5279 				return parent.removeChild(node);
5280 			});
5281 		},
5282 
5283 		setStyle : function(n, na, v) {
5284 			var t = this;
5285 
5286 			return t.run(n, function(e) {
5287 				var s, i;
5288 
5289 				s = e.style;
5290 
5291 				// Camelcase it, if needed
5292 				na = na.replace(/-(\D)/g, function(a, b){
5293 					return b.toUpperCase();
5294 				});
5295 
5296 				// Default px suffix on these
5297 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5298 					v += 'px';
5299 
5300 				switch (na) {
5301 					case 'opacity':
5302 						// IE specific opacity
5303 						if (isIE) {
5304 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5305 
5306 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5307 								s.display = 'inline-block';
5308 						}
5309 
5310 						// Fix for older browsers
5311 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5312 						break;
5313 
5314 					case 'float':
5315 						isIE ? s.styleFloat = v : s.cssFloat = v;
5316 						break;
5317 					
5318 					default:
5319 						s[na] = v || '';
5320 				}
5321 
5322 				// Force update of the style data
5323 				if (t.settings.update_styles)
5324 					t.setAttrib(e, 'data-mce-style');
5325 			});
5326 		},
5327 
5328 		getStyle : function(n, na, c) {
5329 			n = this.get(n);
5330 
5331 			if (!n)
5332 				return;
5333 
5334 			// Gecko
5335 			if (this.doc.defaultView && c) {
5336 				// Remove camelcase
5337 				na = na.replace(/[A-Z]/g, function(a){
5338 					return '-' + a;
5339 				});
5340 
5341 				try {
5342 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5343 				} catch (ex) {
5344 					// Old safari might fail
5345 					return null;
5346 				}
5347 			}
5348 
5349 			// Camelcase it, if needed
5350 			na = na.replace(/-(\D)/g, function(a, b){
5351 				return b.toUpperCase();
5352 			});
5353 
5354 			if (na == 'float')
5355 				na = isIE ? 'styleFloat' : 'cssFloat';
5356 
5357 			// IE & Opera
5358 			if (n.currentStyle && c)
5359 				return n.currentStyle[na];
5360 
5361 			return n.style ? n.style[na] : undefined;
5362 		},
5363 
5364 		setStyles : function(e, o) {
5365 			var t = this, s = t.settings, ol;
5366 
5367 			ol = s.update_styles;
5368 			s.update_styles = 0;
5369 
5370 			each(o, function(v, n) {
5371 				t.setStyle(e, n, v);
5372 			});
5373 
5374 			// Update style info
5375 			s.update_styles = ol;
5376 			if (s.update_styles)
5377 				t.setAttrib(e, s.cssText);
5378 		},
5379 
5380 		removeAllAttribs: function(e) {
5381 			return this.run(e, function(e) {
5382 				var i, attrs = e.attributes;
5383 				for (i = attrs.length - 1; i >= 0; i--) {
5384 					e.removeAttributeNode(attrs.item(i));
5385 				}
5386 			});
5387 		},
5388 
5389 		setAttrib : function(e, n, v) {
5390 			var t = this;
5391 
5392 			// Whats the point
5393 			if (!e || !n)
5394 				return;
5395 
5396 			// Strict XML mode
5397 			if (t.settings.strict)
5398 				n = n.toLowerCase();
5399 
5400 			return this.run(e, function(e) {
5401 				var s = t.settings;
5402 				var originalValue = e.getAttribute(n);
5403 				if (v !== null) {
5404 					switch (n) {
5405 						case "style":
5406 							if (!is(v, 'string')) {
5407 								each(v, function(v, n) {
5408 									t.setStyle(e, n, v);
5409 								});
5410 
5411 								return;
5412 							}
5413 
5414 							// No mce_style for elements with these since they might get resized by the user
5415 							if (s.keep_values) {
5416 								if (v && !t._isRes(v))
5417 									e.setAttribute('data-mce-style', v, 2);
5418 								else
5419 									e.removeAttribute('data-mce-style', 2);
5420 							}
5421 
5422 							e.style.cssText = v;
5423 							break;
5424 
5425 						case "class":
5426 							e.className = v || ''; // Fix IE null bug
5427 							break;
5428 
5429 						case "src":
5430 						case "href":
5431 							if (s.keep_values) {
5432 								if (s.url_converter)
5433 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5434 
5435 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5436 							}
5437 
5438 							break;
5439 
5440 						case "shape":
5441 							e.setAttribute('data-mce-style', v);
5442 							break;
5443 					}
5444 				}
5445 				if (is(v) && v !== null && v.length !== 0)
5446 					e.setAttribute(n, '' + v, 2);
5447 				else
5448 					e.removeAttribute(n, 2);
5449 
5450 				// fire onChangeAttrib event for attributes that have changed
5451 				if (tinyMCE.activeEditor && originalValue != v) {
5452 					var ed = tinyMCE.activeEditor;
5453 					ed.onSetAttrib.dispatch(ed, e, n, v);
5454 				}
5455 			});
5456 		},
5457 
5458 		setAttribs : function(e, o) {
5459 			var t = this;
5460 
5461 			return this.run(e, function(e) {
5462 				each(o, function(v, n) {
5463 					t.setAttrib(e, n, v);
5464 				});
5465 			});
5466 		},
5467 
5468 		getAttrib : function(e, n, dv) {
5469 			var v, t = this, undef;
5470 
5471 			e = t.get(e);
5472 
5473 			if (!e || e.nodeType !== 1)
5474 				return dv === undef ? false : dv;
5475 
5476 			if (!is(dv))
5477 				dv = '';
5478 
5479 			// Try the mce variant for these
5480 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5481 				v = e.getAttribute("data-mce-" + n);
5482 
5483 				if (v)
5484 					return v;
5485 			}
5486 
5487 			if (isIE && t.props[n]) {
5488 				v = e[t.props[n]];
5489 				v = v && v.nodeValue ? v.nodeValue : v;
5490 			}
5491 
5492 			if (!v)
5493 				v = e.getAttribute(n, 2);
5494 
5495 			// Check boolean attribs
5496 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5497 				if (e[t.props[n]] === true && v === '')
5498 					return n;
5499 
5500 				return v ? n : '';
5501 			}
5502 
5503 			// Inner input elements will override attributes on form elements
5504 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5505 				return e.getAttributeNode(n).nodeValue;
5506 
5507 			if (n === 'style') {
5508 				v = v || e.style.cssText;
5509 
5510 				if (v) {
5511 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5512 
5513 					if (t.settings.keep_values && !t._isRes(v))
5514 						e.setAttribute('data-mce-style', v);
5515 				}
5516 			}
5517 
5518 			// Remove Apple and WebKit stuff
5519 			if (isWebKit && n === "class" && v)
5520 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5521 
5522 			// Handle IE issues
5523 			if (isIE) {
5524 				switch (n) {
5525 					case 'rowspan':
5526 					case 'colspan':
5527 						// IE returns 1 as default value
5528 						if (v === 1)
5529 							v = '';
5530 
5531 						break;
5532 
5533 					case 'size':
5534 						// IE returns +0 as default value for size
5535 						if (v === '+0' || v === 20 || v === 0)
5536 							v = '';
5537 
5538 						break;
5539 
5540 					case 'width':
5541 					case 'height':
5542 					case 'vspace':
5543 					case 'checked':
5544 					case 'disabled':
5545 					case 'readonly':
5546 						if (v === 0)
5547 							v = '';
5548 
5549 						break;
5550 
5551 					case 'hspace':
5552 						// IE returns -1 as default value
5553 						if (v === -1)
5554 							v = '';
5555 
5556 						break;
5557 
5558 					case 'maxlength':
5559 					case 'tabindex':
5560 						// IE returns default value
5561 						if (v === 32768 || v === 2147483647 || v === '32768')
5562 							v = '';
5563 
5564 						break;
5565 
5566 					case 'multiple':
5567 					case 'compact':
5568 					case 'noshade':
5569 					case 'nowrap':
5570 						if (v === 65535)
5571 							return n;
5572 
5573 						return dv;
5574 
5575 					case 'shape':
5576 						v = v.toLowerCase();
5577 						break;
5578 
5579 					default:
5580 						// IE has odd anonymous function for event attributes
5581 						if (n.indexOf('on') === 0 && v)
5582 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5583 				}
5584 			}
5585 
5586 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5587 		},
5588 
5589 		getPos : function(n, ro) {
5590 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5591 
5592 			n = t.get(n);
5593 			ro = ro || d.body;
5594 
5595 			if (n) {
5596 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5597 				if (n.getBoundingClientRect) {
5598 					n = n.getBoundingClientRect();
5599 					e = t.boxModel ? d.documentElement : d.body;
5600 
5601 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5602 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5603 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5604 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5605 
5606 					return {x : x, y : y};
5607 				}
5608 
5609 				r = n;
5610 				while (r && r != ro && r.nodeType) {
5611 					x += r.offsetLeft || 0;
5612 					y += r.offsetTop || 0;
5613 					r = r.offsetParent;
5614 				}
5615 
5616 				r = n.parentNode;
5617 				while (r && r != ro && r.nodeType) {
5618 					x -= r.scrollLeft || 0;
5619 					y -= r.scrollTop || 0;
5620 					r = r.parentNode;
5621 				}
5622 			}
5623 
5624 			return {x : x, y : y};
5625 		},
5626 
5627 		parseStyle : function(st) {
5628 			return this.styles.parse(st);
5629 		},
5630 
5631 		serializeStyle : function(o, name) {
5632 			return this.styles.serialize(o, name);
5633 		},
5634 
5635 		addStyle: function(cssText) {
5636 			var doc = this.doc, head;
5637 
5638 			// Create style element if needed
5639 			styleElm = doc.getElementById('mceDefaultStyles');
5640 			if (!styleElm) {
5641 				styleElm = doc.createElement('style'),
5642 				styleElm.id = 'mceDefaultStyles';
5643 				styleElm.type = 'text/css';
5644 
5645 				head = doc.getElementsByTagName('head')[0]
5646 				if (head.firstChild) {
5647 					head.insertBefore(styleElm, head.firstChild);
5648 				} else {
5649 					head.appendChild(styleElm);
5650 				}
5651 			}
5652 
5653 			// Append style data to old or new style element
5654 			if (styleElm.styleSheet) {
5655 				styleElm.styleSheet.cssText += cssText;
5656 			} else {
5657 				styleElm.appendChild(doc.createTextNode(cssText));
5658 			}
5659 		},
5660 
5661 		loadCSS : function(u) {
5662 			var t = this, d = t.doc, head;
5663 
5664 			if (!u)
5665 				u = '';
5666 
5667 			head = d.getElementsByTagName('head')[0];
5668 
5669 			each(u.split(','), function(u) {
5670 				var link;
5671 
5672 				if (t.files[u])
5673 					return;
5674 
5675 				t.files[u] = true;
5676 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5677 
5678 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5679 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5680 				// It's ugly but it seems to work fine.
5681 				if (isIE && d.documentMode && d.recalc) {
5682 					link.onload = function() {
5683 						if (d.recalc)
5684 							d.recalc();
5685 
5686 						link.onload = null;
5687 					};
5688 				}
5689 
5690 				head.appendChild(link);
5691 			});
5692 		},
5693 
5694 		addClass : function(e, c) {
5695 			return this.run(e, function(e) {
5696 				var o;
5697 
5698 				if (!c)
5699 					return 0;
5700 
5701 				if (this.hasClass(e, c))
5702 					return e.className;
5703 
5704 				o = this.removeClass(e, c);
5705 
5706 				return e.className = (o != '' ? (o + ' ') : '') + c;
5707 			});
5708 		},
5709 
5710 		removeClass : function(e, c) {
5711 			var t = this, re;
5712 
5713 			return t.run(e, function(e) {
5714 				var v;
5715 
5716 				if (t.hasClass(e, c)) {
5717 					if (!re)
5718 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5719 
5720 					v = e.className.replace(re, ' ');
5721 					v = tinymce.trim(v != ' ' ? v : '');
5722 
5723 					e.className = v;
5724 
5725 					// Empty class attr
5726 					if (!v) {
5727 						e.removeAttribute('class');
5728 						e.removeAttribute('className');
5729 					}
5730 
5731 					return v;
5732 				}
5733 
5734 				return e.className;
5735 			});
5736 		},
5737 
5738 		hasClass : function(n, c) {
5739 			n = this.get(n);
5740 
5741 			if (!n || !c)
5742 				return false;
5743 
5744 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5745 		},
5746 
5747 		show : function(e) {
5748 			return this.setStyle(e, 'display', 'block');
5749 		},
5750 
5751 		hide : function(e) {
5752 			return this.setStyle(e, 'display', 'none');
5753 		},
5754 
5755 		isHidden : function(e) {
5756 			e = this.get(e);
5757 
5758 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5759 		},
5760 
5761 		uniqueId : function(p) {
5762 			return (!p ? 'mce_' : p) + (this.counter++);
5763 		},
5764 
5765 		setHTML : function(element, html) {
5766 			var self = this;
5767 
5768 			return self.run(element, function(element) {
5769 				if (isIE) {
5770 					// Remove all child nodes, IE keeps empty text nodes in DOM
5771 					while (element.firstChild)
5772 						element.removeChild(element.firstChild);
5773 
5774 					try {
5775 						// IE will remove comments from the beginning
5776 						// unless you padd the contents with something
5777 						element.innerHTML = '<br />' + html;
5778 						element.removeChild(element.firstChild);
5779 					} catch (ex) {
5780 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5781 						// This seems to fix this problem
5782 
5783 						// Create new div with HTML contents and a BR infront to keep comments
5784 						var newElement = self.create('div');
5785 						newElement.innerHTML = '<br />' + html;
5786 
5787 						// Add all children from div to target
5788 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5789 							// Skip br element
5790 							if (i && element.canHaveHTML)
5791 								element.appendChild(node);
5792 						});
5793 					}
5794 				} else
5795 					element.innerHTML = html;
5796 
5797 				return html;
5798 			});
5799 		},
5800 
5801 		getOuterHTML : function(elm) {
5802 			var doc, self = this;
5803 
5804 			elm = self.get(elm);
5805 
5806 			if (!elm)
5807 				return null;
5808 
5809 			if (elm.nodeType === 1 && self.hasOuterHTML)
5810 				return elm.outerHTML;
5811 
5812 			doc = (elm.ownerDocument || self.doc).createElement("body");
5813 			doc.appendChild(elm.cloneNode(true));
5814 
5815 			return doc.innerHTML;
5816 		},
5817 
5818 		setOuterHTML : function(e, h, d) {
5819 			var t = this;
5820 
5821 			function setHTML(e, h, d) {
5822 				var n, tp;
5823 
5824 				tp = d.createElement("body");
5825 				tp.innerHTML = h;
5826 
5827 				n = tp.lastChild;
5828 				while (n) {
5829 					t.insertAfter(n.cloneNode(true), e);
5830 					n = n.previousSibling;
5831 				}
5832 
5833 				t.remove(e);
5834 			};
5835 
5836 			return this.run(e, function(e) {
5837 				e = t.get(e);
5838 
5839 				// Only set HTML on elements
5840 				if (e.nodeType == 1) {
5841 					d = d || e.ownerDocument || t.doc;
5842 
5843 					if (isIE) {
5844 						try {
5845 							// Try outerHTML for IE it sometimes produces an unknown runtime error
5846 							if (isIE && e.nodeType == 1)
5847 								e.outerHTML = h;
5848 							else
5849 								setHTML(e, h, d);
5850 						} catch (ex) {
5851 							// Fix for unknown runtime error
5852 							setHTML(e, h, d);
5853 						}
5854 					} else
5855 						setHTML(e, h, d);
5856 				}
5857 			});
5858 		},
5859 
5860 		decode : Entities.decode,
5861 
5862 		encode : Entities.encodeAllRaw,
5863 
5864 		insertAfter : function(node, reference_node) {
5865 			reference_node = this.get(reference_node);
5866 
5867 			return this.run(node, function(node) {
5868 				var parent, nextSibling;
5869 
5870 				parent = reference_node.parentNode;
5871 				nextSibling = reference_node.nextSibling;
5872 
5873 				if (nextSibling)
5874 					parent.insertBefore(node, nextSibling);
5875 				else
5876 					parent.appendChild(node);
5877 
5878 				return node;
5879 			});
5880 		},
5881 
5882 		replace : function(n, o, k) {
5883 			var t = this;
5884 
5885 			if (is(o, 'array'))
5886 				n = n.cloneNode(true);
5887 
5888 			return t.run(o, function(o) {
5889 				if (k) {
5890 					each(tinymce.grep(o.childNodes), function(c) {
5891 						n.appendChild(c);
5892 					});
5893 				}
5894 
5895 				return o.parentNode.replaceChild(n, o);
5896 			});
5897 		},
5898 
5899 		rename : function(elm, name) {
5900 			var t = this, newElm;
5901 
5902 			if (elm.nodeName != name.toUpperCase()) {
5903 				// Rename block element
5904 				newElm = t.create(name);
5905 
5906 				// Copy attribs to new block
5907 				each(t.getAttribs(elm), function(attr_node) {
5908 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
5909 				});
5910 
5911 				// Replace block
5912 				t.replace(newElm, elm, 1);
5913 			}
5914 
5915 			return newElm || elm;
5916 		},
5917 
5918 		findCommonAncestor : function(a, b) {
5919 			var ps = a, pe;
5920 
5921 			while (ps) {
5922 				pe = b;
5923 
5924 				while (pe && ps != pe)
5925 					pe = pe.parentNode;
5926 
5927 				if (ps == pe)
5928 					break;
5929 
5930 				ps = ps.parentNode;
5931 			}
5932 
5933 			if (!ps && a.ownerDocument)
5934 				return a.ownerDocument.documentElement;
5935 
5936 			return ps;
5937 		},
5938 
5939 		toHex : function(s) {
5940 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
5941 
5942 			function hex(s) {
5943 				s = parseInt(s, 10).toString(16);
5944 
5945 				return s.length > 1 ? s : '0' + s; // 0 -> 00
5946 			};
5947 
5948 			if (c) {
5949 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
5950 
5951 				return s;
5952 			}
5953 
5954 			return s;
5955 		},
5956 
5957 		getClasses : function() {
5958 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
5959 
5960 			if (t.classes)
5961 				return t.classes;
5962 
5963 			function addClasses(s) {
5964 				// IE style imports
5965 				each(s.imports, function(r) {
5966 					addClasses(r);
5967 				});
5968 
5969 				each(s.cssRules || s.rules, function(r) {
5970 					// Real type or fake it on IE
5971 					switch (r.type || 1) {
5972 						// Rule
5973 						case 1:
5974 							if (r.selectorText) {
5975 								each(r.selectorText.split(','), function(v) {
5976 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
5977 
5978 									// Is internal or it doesn't contain a class
5979 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
5980 										return;
5981 
5982 									// Remove everything but class name
5983 									ov = v;
5984 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
5985 
5986 									// Filter classes
5987 									if (f && !(v = f(v, ov)))
5988 										return;
5989 
5990 									if (!lo[v]) {
5991 										cl.push({'class' : v});
5992 										lo[v] = 1;
5993 									}
5994 								});
5995 							}
5996 							break;
5997 
5998 						// Import
5999 						case 3:
6000 							addClasses(r.styleSheet);
6001 							break;
6002 					}
6003 				});
6004 			};
6005 
6006 			try {
6007 				each(t.doc.styleSheets, addClasses);
6008 			} catch (ex) {
6009 				// Ignore
6010 			}
6011 
6012 			if (cl.length > 0)
6013 				t.classes = cl;
6014 
6015 			return cl;
6016 		},
6017 
6018 		run : function(e, f, s) {
6019 			var t = this, o;
6020 
6021 			if (t.doc && typeof(e) === 'string')
6022 				e = t.get(e);
6023 
6024 			if (!e)
6025 				return false;
6026 
6027 			s = s || this;
6028 			if (!e.nodeType && (e.length || e.length === 0)) {
6029 				o = [];
6030 
6031 				each(e, function(e, i) {
6032 					if (e) {
6033 						if (typeof(e) == 'string')
6034 							e = t.doc.getElementById(e);
6035 
6036 						o.push(f.call(s, e, i));
6037 					}
6038 				});
6039 
6040 				return o;
6041 			}
6042 
6043 			return f.call(s, e);
6044 		},
6045 
6046 		getAttribs : function(n) {
6047 			var o;
6048 
6049 			n = this.get(n);
6050 
6051 			if (!n)
6052 				return [];
6053 
6054 			if (isIE) {
6055 				o = [];
6056 
6057 				// Object will throw exception in IE
6058 				if (n.nodeName == 'OBJECT')
6059 					return n.attributes;
6060 
6061 				// IE doesn't keep the selected attribute if you clone option elements
6062 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6063 					o.push({specified : 1, nodeName : 'selected'});
6064 
6065 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6066 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6067 					o.push({specified : 1, nodeName : a});
6068 				});
6069 
6070 				return o;
6071 			}
6072 
6073 			return n.attributes;
6074 		},
6075 
6076 		isEmpty : function(node, elements) {
6077 			var self = this, i, attributes, type, walker, name, brCount = 0;
6078 
6079 			node = node.firstChild;
6080 			if (node) {
6081 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6082 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6083 
6084 				do {
6085 					type = node.nodeType;
6086 
6087 					if (type === 1) {
6088 						// Ignore bogus elements
6089 						if (node.getAttribute('data-mce-bogus'))
6090 							continue;
6091 
6092 						// Keep empty elements like <img />
6093 						name = node.nodeName.toLowerCase();
6094 						if (elements && elements[name]) {
6095 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6096 							if (name === 'br') {
6097 								brCount++;
6098 								continue;
6099 							}
6100 
6101 							return false;
6102 						}
6103 
6104 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6105 						attributes = self.getAttribs(node);
6106 						i = node.attributes.length;
6107 						while (i--) {
6108 							name = node.attributes[i].nodeName;
6109 							if (name === "name" || name === 'data-mce-bookmark')
6110 								return false;
6111 						}
6112 					}
6113 
6114 					// Keep comment nodes
6115 					if (type == 8)
6116 						return false;
6117 
6118 					// Keep non whitespace text nodes
6119 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6120 						return false;
6121 				} while (node = walker.next());
6122 			}
6123 
6124 			return brCount <= 1;
6125 		},
6126 
6127 		destroy : function(s) {
6128 			var t = this;
6129 
6130 			t.win = t.doc = t.root = t.events = t.frag = null;
6131 
6132 			// Manual destroy then remove unload handler
6133 			if (!s)
6134 				tinymce.removeUnload(t.destroy);
6135 		},
6136 
6137 		createRng : function() {
6138 			var d = this.doc;
6139 
6140 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6141 		},
6142 
6143 		nodeIndex : function(node, normalized) {
6144 			var idx = 0, lastNodeType, lastNode, nodeType;
6145 
6146 			if (node) {
6147 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6148 					nodeType = node.nodeType;
6149 
6150 					// Normalize text nodes
6151 					if (normalized && nodeType == 3) {
6152 						if (nodeType == lastNodeType || !node.nodeValue.length)
6153 							continue;
6154 					}
6155 					idx++;
6156 					lastNodeType = nodeType;
6157 				}
6158 			}
6159 
6160 			return idx;
6161 		},
6162 
6163 		split : function(pe, e, re) {
6164 			var t = this, r = t.createRng(), bef, aft, pa;
6165 
6166 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6167 			// but we don't want that in our code since it serves no purpose for the end user
6168 			// For example if this is chopped:
6169 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6170 			// would produce:
6171 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6172 			// this function will then trim of empty edges and produce:
6173 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6174 			function trim(node) {
6175 				var i, children = node.childNodes, type = node.nodeType;
6176 
6177 				function surroundedBySpans(node) {
6178 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6179 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6180 					return previousIsSpan && nextIsSpan;
6181 				}
6182 
6183 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6184 					return;
6185 
6186 				for (i = children.length - 1; i >= 0; i--)
6187 					trim(children[i]);
6188 
6189 				if (type != 9) {
6190 					// Keep non whitespace text nodes
6191 					if (type == 3 && node.nodeValue.length > 0) {
6192 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6193 						// Also keep text nodes with only spaces if surrounded by spans.
6194 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6195 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6196 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6197 							return;
6198 					} else if (type == 1) {
6199 						// If the only child is a bookmark then move it up
6200 						children = node.childNodes;
6201 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6202 							node.parentNode.insertBefore(children[0], node);
6203 
6204 						// Keep non empty elements or img, hr etc
6205 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6206 							return;
6207 					}
6208 
6209 					t.remove(node);
6210 				}
6211 
6212 				return node;
6213 			};
6214 
6215 			if (pe && e) {
6216 				// Get before chunk
6217 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6218 				r.setEnd(e.parentNode, t.nodeIndex(e));
6219 				bef = r.extractContents();
6220 
6221 				// Get after chunk
6222 				r = t.createRng();
6223 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6224 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6225 				aft = r.extractContents();
6226 
6227 				// Insert before chunk
6228 				pa = pe.parentNode;
6229 				pa.insertBefore(trim(bef), pe);
6230 
6231 				// Insert middle chunk
6232 				if (re)
6233 				pa.replaceChild(re, e);
6234 			else
6235 				pa.insertBefore(e, pe);
6236 
6237 				// Insert after chunk
6238 				pa.insertBefore(trim(aft), pe);
6239 				t.remove(pe);
6240 
6241 				return re || e;
6242 			}
6243 		},
6244 
6245 		bind : function(target, name, func, scope) {
6246 			return this.events.add(target, name, func, scope || this);
6247 		},
6248 
6249 		unbind : function(target, name, func) {
6250 			return this.events.remove(target, name, func);
6251 		},
6252 
6253 		fire : function(target, name, evt) {
6254 			return this.events.fire(target, name, evt);
6255 		},
6256 
6257 		// Returns the content editable state of a node
6258 		getContentEditable: function(node) {
6259 			var contentEditable;
6260 
6261 			// Check type
6262 			if (node.nodeType != 1) {
6263 				return null;
6264 			}
6265 
6266 			// Check for fake content editable
6267 			contentEditable = node.getAttribute("data-mce-contenteditable");
6268 			if (contentEditable && contentEditable !== "inherit") {
6269 				return contentEditable;
6270 			}
6271 
6272 			// Check for real content editable
6273 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6274 		},
6275 
6276 
6277 		_findSib : function(node, selector, name) {
6278 			var t = this, f = selector;
6279 
6280 			if (node) {
6281 				// If expression make a function of it using is
6282 				if (is(f, 'string')) {
6283 					f = function(node) {
6284 						return t.is(node, selector);
6285 					};
6286 				}
6287 
6288 				// Loop all siblings
6289 				for (node = node[name]; node; node = node[name]) {
6290 					if (f(node))
6291 						return node;
6292 				}
6293 			}
6294 
6295 			return null;
6296 		},
6297 
6298 		_isRes : function(c) {
6299 			// Is live resizble element
6300 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6301 		}
6302 
6303 		/*
6304 		walk : function(n, f, s) {
6305 			var d = this.doc, w;
6306 
6307 			if (d.createTreeWalker) {
6308 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6309 
6310 				while ((n = w.nextNode()) != null)
6311 					f.call(s || this, n);
6312 			} else
6313 				tinymce.walk(n, f, 'childNodes', s);
6314 		}
6315 		*/
6316 
6317 		/*
6318 		toRGB : function(s) {
6319 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6320 
6321 			if (c) {
6322 				// #FFF -> #FFFFFF
6323 				if (!is(c[3]))
6324 					c[3] = c[2] = c[1];
6325 
6326 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6327 			}
6328 
6329 			return s;
6330 		}
6331 		*/
6332 	});
6333 
6334 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6335 })(tinymce);
6336 
6337 (function(ns) {
6338 	// Range constructor
6339 	function Range(dom) {
6340 		var t = this,
6341 			doc = dom.doc,
6342 			EXTRACT = 0,
6343 			CLONE = 1,
6344 			DELETE = 2,
6345 			TRUE = true,
6346 			FALSE = false,
6347 			START_OFFSET = 'startOffset',
6348 			START_CONTAINER = 'startContainer',
6349 			END_CONTAINER = 'endContainer',
6350 			END_OFFSET = 'endOffset',
6351 			extend = tinymce.extend,
6352 			nodeIndex = dom.nodeIndex;
6353 
6354 		extend(t, {
6355 			// Inital states
6356 			startContainer : doc,
6357 			startOffset : 0,
6358 			endContainer : doc,
6359 			endOffset : 0,
6360 			collapsed : TRUE,
6361 			commonAncestorContainer : doc,
6362 
6363 			// Range constants
6364 			START_TO_START : 0,
6365 			START_TO_END : 1,
6366 			END_TO_END : 2,
6367 			END_TO_START : 3,
6368 
6369 			// Public methods
6370 			setStart : setStart,
6371 			setEnd : setEnd,
6372 			setStartBefore : setStartBefore,
6373 			setStartAfter : setStartAfter,
6374 			setEndBefore : setEndBefore,
6375 			setEndAfter : setEndAfter,
6376 			collapse : collapse,
6377 			selectNode : selectNode,
6378 			selectNodeContents : selectNodeContents,
6379 			compareBoundaryPoints : compareBoundaryPoints,
6380 			deleteContents : deleteContents,
6381 			extractContents : extractContents,
6382 			cloneContents : cloneContents,
6383 			insertNode : insertNode,
6384 			surroundContents : surroundContents,
6385 			cloneRange : cloneRange,
6386 			toStringIE : toStringIE
6387 		});
6388 
6389 		function createDocumentFragment() {
6390 			return doc.createDocumentFragment();
6391 		};
6392 
6393 		function setStart(n, o) {
6394 			_setEndPoint(TRUE, n, o);
6395 		};
6396 
6397 		function setEnd(n, o) {
6398 			_setEndPoint(FALSE, n, o);
6399 		};
6400 
6401 		function setStartBefore(n) {
6402 			setStart(n.parentNode, nodeIndex(n));
6403 		};
6404 
6405 		function setStartAfter(n) {
6406 			setStart(n.parentNode, nodeIndex(n) + 1);
6407 		};
6408 
6409 		function setEndBefore(n) {
6410 			setEnd(n.parentNode, nodeIndex(n));
6411 		};
6412 
6413 		function setEndAfter(n) {
6414 			setEnd(n.parentNode, nodeIndex(n) + 1);
6415 		};
6416 
6417 		function collapse(ts) {
6418 			if (ts) {
6419 				t[END_CONTAINER] = t[START_CONTAINER];
6420 				t[END_OFFSET] = t[START_OFFSET];
6421 			} else {
6422 				t[START_CONTAINER] = t[END_CONTAINER];
6423 				t[START_OFFSET] = t[END_OFFSET];
6424 			}
6425 
6426 			t.collapsed = TRUE;
6427 		};
6428 
6429 		function selectNode(n) {
6430 			setStartBefore(n);
6431 			setEndAfter(n);
6432 		};
6433 
6434 		function selectNodeContents(n) {
6435 			setStart(n, 0);
6436 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6437 		};
6438 
6439 		function compareBoundaryPoints(h, r) {
6440 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6441 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6442 
6443 			// Check START_TO_START
6444 			if (h === 0)
6445 				return _compareBoundaryPoints(sc, so, rsc, rso);
6446 	
6447 			// Check START_TO_END
6448 			if (h === 1)
6449 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6450 	
6451 			// Check END_TO_END
6452 			if (h === 2)
6453 				return _compareBoundaryPoints(ec, eo, rec, reo);
6454 	
6455 			// Check END_TO_START
6456 			if (h === 3) 
6457 				return _compareBoundaryPoints(sc, so, rec, reo);
6458 		};
6459 
6460 		function deleteContents() {
6461 			_traverse(DELETE);
6462 		};
6463 
6464 		function extractContents() {
6465 			return _traverse(EXTRACT);
6466 		};
6467 
6468 		function cloneContents() {
6469 			return _traverse(CLONE);
6470 		};
6471 
6472 		function insertNode(n) {
6473 			var startContainer = this[START_CONTAINER],
6474 				startOffset = this[START_OFFSET], nn, o;
6475 
6476 			// Node is TEXT_NODE or CDATA
6477 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6478 				if (!startOffset) {
6479 					// At the start of text
6480 					startContainer.parentNode.insertBefore(n, startContainer);
6481 				} else if (startOffset >= startContainer.nodeValue.length) {
6482 					// At the end of text
6483 					dom.insertAfter(n, startContainer);
6484 				} else {
6485 					// Middle, need to split
6486 					nn = startContainer.splitText(startOffset);
6487 					startContainer.parentNode.insertBefore(n, nn);
6488 				}
6489 			} else {
6490 				// Insert element node
6491 				if (startContainer.childNodes.length > 0)
6492 					o = startContainer.childNodes[startOffset];
6493 
6494 				if (o)
6495 					startContainer.insertBefore(n, o);
6496 				else
6497 					startContainer.appendChild(n);
6498 			}
6499 		};
6500 
6501 		function surroundContents(n) {
6502 			var f = t.extractContents();
6503 
6504 			t.insertNode(n);
6505 			n.appendChild(f);
6506 			t.selectNode(n);
6507 		};
6508 
6509 		function cloneRange() {
6510 			return extend(new Range(dom), {
6511 				startContainer : t[START_CONTAINER],
6512 				startOffset : t[START_OFFSET],
6513 				endContainer : t[END_CONTAINER],
6514 				endOffset : t[END_OFFSET],
6515 				collapsed : t.collapsed,
6516 				commonAncestorContainer : t.commonAncestorContainer
6517 			});
6518 		};
6519 
6520 		// Private methods
6521 
6522 		function _getSelectedNode(container, offset) {
6523 			var child;
6524 
6525 			if (container.nodeType == 3 /* TEXT_NODE */)
6526 				return container;
6527 
6528 			if (offset < 0)
6529 				return container;
6530 
6531 			child = container.firstChild;
6532 			while (child && offset > 0) {
6533 				--offset;
6534 				child = child.nextSibling;
6535 			}
6536 
6537 			if (child)
6538 				return child;
6539 
6540 			return container;
6541 		};
6542 
6543 		function _isCollapsed() {
6544 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6545 		};
6546 
6547 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6548 			var c, offsetC, n, cmnRoot, childA, childB;
6549 			
6550 			// In the first case the boundary-points have the same container. A is before B
6551 			// if its offset is less than the offset of B, A is equal to B if its offset is
6552 			// equal to the offset of B, and A is after B if its offset is greater than the
6553 			// offset of B.
6554 			if (containerA == containerB) {
6555 				if (offsetA == offsetB)
6556 					return 0; // equal
6557 
6558 				if (offsetA < offsetB)
6559 					return -1; // before
6560 
6561 				return 1; // after
6562 			}
6563 
6564 			// In the second case a child node C of the container of A is an ancestor
6565 			// container of B. In this case, A is before B if the offset of A is less than or
6566 			// equal to the index of the child node C and A is after B otherwise.
6567 			c = containerB;
6568 			while (c && c.parentNode != containerA)
6569 				c = c.parentNode;
6570 
6571 			if (c) {
6572 				offsetC = 0;
6573 				n = containerA.firstChild;
6574 
6575 				while (n != c && offsetC < offsetA) {
6576 					offsetC++;
6577 					n = n.nextSibling;
6578 				}
6579 
6580 				if (offsetA <= offsetC)
6581 					return -1; // before
6582 
6583 				return 1; // after
6584 			}
6585 
6586 			// In the third case a child node C of the container of B is an ancestor container
6587 			// of A. In this case, A is before B if the index of the child node C is less than
6588 			// the offset of B and A is after B otherwise.
6589 			c = containerA;
6590 			while (c && c.parentNode != containerB) {
6591 				c = c.parentNode;
6592 			}
6593 
6594 			if (c) {
6595 				offsetC = 0;
6596 				n = containerB.firstChild;
6597 
6598 				while (n != c && offsetC < offsetB) {
6599 					offsetC++;
6600 					n = n.nextSibling;
6601 				}
6602 
6603 				if (offsetC < offsetB)
6604 					return -1; // before
6605 
6606 				return 1; // after
6607 			}
6608 
6609 			// In the fourth case, none of three other cases hold: the containers of A and B
6610 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6611 			// the container of A is before the container of B in a pre-order traversal of the
6612 			// Ranges' context tree and A is after B otherwise.
6613 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6614 			childA = containerA;
6615 
6616 			while (childA && childA.parentNode != cmnRoot)
6617 				childA = childA.parentNode;
6618 
6619 			if (!childA)
6620 				childA = cmnRoot;
6621 
6622 			childB = containerB;
6623 			while (childB && childB.parentNode != cmnRoot)
6624 				childB = childB.parentNode;
6625 
6626 			if (!childB)
6627 				childB = cmnRoot;
6628 
6629 			if (childA == childB)
6630 				return 0; // equal
6631 
6632 			n = cmnRoot.firstChild;
6633 			while (n) {
6634 				if (n == childA)
6635 					return -1; // before
6636 
6637 				if (n == childB)
6638 					return 1; // after
6639 
6640 				n = n.nextSibling;
6641 			}
6642 		};
6643 
6644 		function _setEndPoint(st, n, o) {
6645 			var ec, sc;
6646 
6647 			if (st) {
6648 				t[START_CONTAINER] = n;
6649 				t[START_OFFSET] = o;
6650 			} else {
6651 				t[END_CONTAINER] = n;
6652 				t[END_OFFSET] = o;
6653 			}
6654 
6655 			// If one boundary-point of a Range is set to have a root container
6656 			// other than the current one for the Range, the Range is collapsed to
6657 			// the new position. This enforces the restriction that both boundary-
6658 			// points of a Range must have the same root container.
6659 			ec = t[END_CONTAINER];
6660 			while (ec.parentNode)
6661 				ec = ec.parentNode;
6662 
6663 			sc = t[START_CONTAINER];
6664 			while (sc.parentNode)
6665 				sc = sc.parentNode;
6666 
6667 			if (sc == ec) {
6668 				// The start position of a Range is guaranteed to never be after the
6669 				// end position. To enforce this restriction, if the start is set to
6670 				// be at a position after the end, the Range is collapsed to that
6671 				// position.
6672 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6673 					t.collapse(st);
6674 			} else
6675 				t.collapse(st);
6676 
6677 			t.collapsed = _isCollapsed();
6678 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6679 		};
6680 
6681 		function _traverse(how) {
6682 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6683 
6684 			if (t[START_CONTAINER] == t[END_CONTAINER])
6685 				return _traverseSameContainer(how);
6686 
6687 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6688 				if (p == t[START_CONTAINER])
6689 					return _traverseCommonStartContainer(c, how);
6690 
6691 				++endContainerDepth;
6692 			}
6693 
6694 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6695 				if (p == t[END_CONTAINER])
6696 					return _traverseCommonEndContainer(c, how);
6697 
6698 				++startContainerDepth;
6699 			}
6700 
6701 			depthDiff = startContainerDepth - endContainerDepth;
6702 
6703 			startNode = t[START_CONTAINER];
6704 			while (depthDiff > 0) {
6705 				startNode = startNode.parentNode;
6706 				depthDiff--;
6707 			}
6708 
6709 			endNode = t[END_CONTAINER];
6710 			while (depthDiff < 0) {
6711 				endNode = endNode.parentNode;
6712 				depthDiff++;
6713 			}
6714 
6715 			// ascend the ancestor hierarchy until we have a common parent.
6716 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6717 				startNode = sp;
6718 				endNode = ep;
6719 			}
6720 
6721 			return _traverseCommonAncestors(startNode, endNode, how);
6722 		};
6723 
6724 		 function _traverseSameContainer(how) {
6725 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6726 
6727 			if (how != DELETE)
6728 				frag = createDocumentFragment();
6729 
6730 			// If selection is empty, just return the fragment
6731 			if (t[START_OFFSET] == t[END_OFFSET])
6732 				return frag;
6733 
6734 			// Text node needs special case handling
6735 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6736 				// get the substring
6737 				s = t[START_CONTAINER].nodeValue;
6738 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6739 
6740 				// set the original text node to its new value
6741 				if (how != CLONE) {
6742 					n = t[START_CONTAINER];
6743 					start = t[START_OFFSET];
6744 					len = t[END_OFFSET] - t[START_OFFSET];
6745 
6746 					if (start === 0 && len >= n.nodeValue.length - 1) {
6747 						n.parentNode.removeChild(n);
6748 					} else {
6749 						n.deleteData(start, len);
6750 					}
6751 
6752 					// Nothing is partially selected, so collapse to start point
6753 					t.collapse(TRUE);
6754 				}
6755 
6756 				if (how == DELETE)
6757 					return;
6758 
6759 				if (sub.length > 0) {
6760 					frag.appendChild(doc.createTextNode(sub));
6761 				}
6762 
6763 				return frag;
6764 			}
6765 
6766 			// Copy nodes between the start/end offsets.
6767 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6768 			cnt = t[END_OFFSET] - t[START_OFFSET];
6769 
6770 			while (n && cnt > 0) {
6771 				sibling = n.nextSibling;
6772 				xferNode = _traverseFullySelected(n, how);
6773 
6774 				if (frag)
6775 					frag.appendChild( xferNode );
6776 
6777 				--cnt;
6778 				n = sibling;
6779 			}
6780 
6781 			// Nothing is partially selected, so collapse to start point
6782 			if (how != CLONE)
6783 				t.collapse(TRUE);
6784 
6785 			return frag;
6786 		};
6787 
6788 		function _traverseCommonStartContainer(endAncestor, how) {
6789 			var frag, n, endIdx, cnt, sibling, xferNode;
6790 
6791 			if (how != DELETE)
6792 				frag = createDocumentFragment();
6793 
6794 			n = _traverseRightBoundary(endAncestor, how);
6795 
6796 			if (frag)
6797 				frag.appendChild(n);
6798 
6799 			endIdx = nodeIndex(endAncestor);
6800 			cnt = endIdx - t[START_OFFSET];
6801 
6802 			if (cnt <= 0) {
6803 				// Collapse to just before the endAncestor, which
6804 				// is partially selected.
6805 				if (how != CLONE) {
6806 					t.setEndBefore(endAncestor);
6807 					t.collapse(FALSE);
6808 				}
6809 
6810 				return frag;
6811 			}
6812 
6813 			n = endAncestor.previousSibling;
6814 			while (cnt > 0) {
6815 				sibling = n.previousSibling;
6816 				xferNode = _traverseFullySelected(n, how);
6817 
6818 				if (frag)
6819 					frag.insertBefore(xferNode, frag.firstChild);
6820 
6821 				--cnt;
6822 				n = sibling;
6823 			}
6824 
6825 			// Collapse to just before the endAncestor, which
6826 			// is partially selected.
6827 			if (how != CLONE) {
6828 				t.setEndBefore(endAncestor);
6829 				t.collapse(FALSE);
6830 			}
6831 
6832 			return frag;
6833 		};
6834 
6835 		function _traverseCommonEndContainer(startAncestor, how) {
6836 			var frag, startIdx, n, cnt, sibling, xferNode;
6837 
6838 			if (how != DELETE)
6839 				frag = createDocumentFragment();
6840 
6841 			n = _traverseLeftBoundary(startAncestor, how);
6842 			if (frag)
6843 				frag.appendChild(n);
6844 
6845 			startIdx = nodeIndex(startAncestor);
6846 			++startIdx; // Because we already traversed it
6847 
6848 			cnt = t[END_OFFSET] - startIdx;
6849 			n = startAncestor.nextSibling;
6850 			while (n && cnt > 0) {
6851 				sibling = n.nextSibling;
6852 				xferNode = _traverseFullySelected(n, how);
6853 
6854 				if (frag)
6855 					frag.appendChild(xferNode);
6856 
6857 				--cnt;
6858 				n = sibling;
6859 			}
6860 
6861 			if (how != CLONE) {
6862 				t.setStartAfter(startAncestor);
6863 				t.collapse(TRUE);
6864 			}
6865 
6866 			return frag;
6867 		};
6868 
6869 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
6870 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
6871 
6872 			if (how != DELETE)
6873 				frag = createDocumentFragment();
6874 
6875 			n = _traverseLeftBoundary(startAncestor, how);
6876 			if (frag)
6877 				frag.appendChild(n);
6878 
6879 			commonParent = startAncestor.parentNode;
6880 			startOffset = nodeIndex(startAncestor);
6881 			endOffset = nodeIndex(endAncestor);
6882 			++startOffset;
6883 
6884 			cnt = endOffset - startOffset;
6885 			sibling = startAncestor.nextSibling;
6886 
6887 			while (cnt > 0) {
6888 				nextSibling = sibling.nextSibling;
6889 				n = _traverseFullySelected(sibling, how);
6890 
6891 				if (frag)
6892 					frag.appendChild(n);
6893 
6894 				sibling = nextSibling;
6895 				--cnt;
6896 			}
6897 
6898 			n = _traverseRightBoundary(endAncestor, how);
6899 
6900 			if (frag)
6901 				frag.appendChild(n);
6902 
6903 			if (how != CLONE) {
6904 				t.setStartAfter(startAncestor);
6905 				t.collapse(TRUE);
6906 			}
6907 
6908 			return frag;
6909 		};
6910 
6911 		function _traverseRightBoundary(root, how) {
6912 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
6913 
6914 			if (next == root)
6915 				return _traverseNode(next, isFullySelected, FALSE, how);
6916 
6917 			parent = next.parentNode;
6918 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
6919 
6920 			while (parent) {
6921 				while (next) {
6922 					prevSibling = next.previousSibling;
6923 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
6924 
6925 					if (how != DELETE)
6926 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
6927 
6928 					isFullySelected = TRUE;
6929 					next = prevSibling;
6930 				}
6931 
6932 				if (parent == root)
6933 					return clonedParent;
6934 
6935 				next = parent.previousSibling;
6936 				parent = parent.parentNode;
6937 
6938 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
6939 
6940 				if (how != DELETE)
6941 					clonedGrandParent.appendChild(clonedParent);
6942 
6943 				clonedParent = clonedGrandParent;
6944 			}
6945 		};
6946 
6947 		function _traverseLeftBoundary(root, how) {
6948 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
6949 
6950 			if (next == root)
6951 				return _traverseNode(next, isFullySelected, TRUE, how);
6952 
6953 			parent = next.parentNode;
6954 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
6955 
6956 			while (parent) {
6957 				while (next) {
6958 					nextSibling = next.nextSibling;
6959 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
6960 
6961 					if (how != DELETE)
6962 						clonedParent.appendChild(clonedChild);
6963 
6964 					isFullySelected = TRUE;
6965 					next = nextSibling;
6966 				}
6967 
6968 				if (parent == root)
6969 					return clonedParent;
6970 
6971 				next = parent.nextSibling;
6972 				parent = parent.parentNode;
6973 
6974 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
6975 
6976 				if (how != DELETE)
6977 					clonedGrandParent.appendChild(clonedParent);
6978 
6979 				clonedParent = clonedGrandParent;
6980 			}
6981 		};
6982 
6983 		function _traverseNode(n, isFullySelected, isLeft, how) {
6984 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
6985 
6986 			if (isFullySelected)
6987 				return _traverseFullySelected(n, how);
6988 
6989 			if (n.nodeType == 3 /* TEXT_NODE */) {
6990 				txtValue = n.nodeValue;
6991 
6992 				if (isLeft) {
6993 					offset = t[START_OFFSET];
6994 					newNodeValue = txtValue.substring(offset);
6995 					oldNodeValue = txtValue.substring(0, offset);
6996 				} else {
6997 					offset = t[END_OFFSET];
6998 					newNodeValue = txtValue.substring(0, offset);
6999 					oldNodeValue = txtValue.substring(offset);
7000 				}
7001 
7002 				if (how != CLONE)
7003 					n.nodeValue = oldNodeValue;
7004 
7005 				if (how == DELETE)
7006 					return;
7007 
7008 				newNode = dom.clone(n, FALSE);
7009 				newNode.nodeValue = newNodeValue;
7010 
7011 				return newNode;
7012 			}
7013 
7014 			if (how == DELETE)
7015 				return;
7016 
7017 			return dom.clone(n, FALSE);
7018 		};
7019 
7020 		function _traverseFullySelected(n, how) {
7021 			if (how != DELETE)
7022 				return how == CLONE ? dom.clone(n, TRUE) : n;
7023 
7024 			n.parentNode.removeChild(n);
7025 		};
7026 
7027 		function toStringIE() {
7028 			return dom.create('body', null, cloneContents()).outerText;
7029 		}
7030 		
7031 		return t;
7032 	};
7033 
7034 	ns.Range = Range;
7035 
7036 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7037 	Range.prototype.toString = function() {
7038 		return this.toStringIE();
7039 	};
7040 })(tinymce.dom);
7041 
7042 (function() {
7043 	function Selection(selection) {
7044 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7045 
7046 		function getPosition(rng, start) {
7047 			var checkRng, startIndex = 0, endIndex, inside,
7048 				children, child, offset, index, position = -1, parent;
7049 
7050 			// Setup test range, collapse it and get the parent
7051 			checkRng = rng.duplicate();
7052 			checkRng.collapse(start);
7053 			parent = checkRng.parentElement();
7054 
7055 			// Check if the selection is within the right document
7056 			if (parent.ownerDocument !== selection.dom.doc)
7057 				return;
7058 
7059 			// IE will report non editable elements as it's parent so look for an editable one
7060 			while (parent.contentEditable === "false") {
7061 				parent = parent.parentNode;
7062 			}
7063 
7064 			// If parent doesn't have any children then return that we are inside the element
7065 			if (!parent.hasChildNodes()) {
7066 				return {node : parent, inside : 1};
7067 			}
7068 
7069 			// Setup node list and endIndex
7070 			children = parent.children;
7071 			endIndex = children.length - 1;
7072 
7073 			// Perform a binary search for the position
7074 			while (startIndex <= endIndex) {
7075 				index = Math.floor((startIndex + endIndex) / 2);
7076 
7077 				// Move selection to node and compare the ranges
7078 				child = children[index];
7079 				checkRng.moveToElementText(child);
7080 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7081 
7082 				// Before/after or an exact match
7083 				if (position > 0) {
7084 					endIndex = index - 1;
7085 				} else if (position < 0) {
7086 					startIndex = index + 1;
7087 				} else {
7088 					return {node : child};
7089 				}
7090 			}
7091 
7092 			// Check if child position is before or we didn't find a position
7093 			if (position < 0) {
7094 				// No element child was found use the parent element and the offset inside that
7095 				if (!child) {
7096 					checkRng.moveToElementText(parent);
7097 					checkRng.collapse(true);
7098 					child = parent;
7099 					inside = true;
7100 				} else
7101 					checkRng.collapse(false);
7102 
7103 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7104 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7105 				offset = 0;
7106 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7107 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7108 						break;
7109 					}
7110 
7111 					offset++;
7112 				}
7113 			} else {
7114 				// Child position is after the selection endpoint
7115 				checkRng.collapse(true);
7116 
7117 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7118 				offset = 0;
7119 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7120 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7121 						break;
7122 					}
7123 
7124 					offset++;
7125 				}
7126 			}
7127 
7128 			return {node : child, position : position, offset : offset, inside : inside};
7129 		};
7130 
7131 		// Returns a W3C DOM compatible range object by using the IE Range API
7132 		function getRange() {
7133 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7134 
7135 			// If selection is outside the current document just return an empty range
7136 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7137 			if (element.ownerDocument != dom.doc)
7138 				return domRange;
7139 
7140 			collapsed = selection.isCollapsed();
7141 
7142 			// Handle control selection
7143 			if (ieRange.item) {
7144 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7145 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7146 
7147 				return domRange;
7148 			}
7149 
7150 			function findEndPoint(start) {
7151 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7152 
7153 				container = endPoint.node;
7154 				offset = endPoint.offset;
7155 
7156 				if (endPoint.inside && !container.hasChildNodes()) {
7157 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7158 					return;
7159 				}
7160 
7161 				if (offset === undef) {
7162 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7163 					return;
7164 				}
7165 
7166 				if (endPoint.position < 0) {
7167 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7168 
7169 					if (!sibling) {
7170 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7171 						return;
7172 					}
7173 
7174 					if (!offset) {
7175 						if (sibling.nodeType == 3)
7176 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7177 						else
7178 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7179 
7180 						return;
7181 					}
7182 
7183 					// Find the text node and offset
7184 					while (sibling) {
7185 						nodeValue = sibling.nodeValue;
7186 						textNodeOffset += nodeValue.length;
7187 
7188 						// We are at or passed the position we where looking for
7189 						if (textNodeOffset >= offset) {
7190 							container = sibling;
7191 							textNodeOffset -= offset;
7192 							textNodeOffset = nodeValue.length - textNodeOffset;
7193 							break;
7194 						}
7195 
7196 						sibling = sibling.nextSibling;
7197 					}
7198 				} else {
7199 					// Find the text node and offset
7200 					sibling = container.previousSibling;
7201 
7202 					if (!sibling)
7203 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7204 
7205 					// If there isn't any text to loop then use the first position
7206 					if (!offset) {
7207 						if (container.nodeType == 3)
7208 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7209 						else
7210 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7211 
7212 						return;
7213 					}
7214 
7215 					while (sibling) {
7216 						textNodeOffset += sibling.nodeValue.length;
7217 
7218 						// We are at or passed the position we where looking for
7219 						if (textNodeOffset >= offset) {
7220 							container = sibling;
7221 							textNodeOffset -= offset;
7222 							break;
7223 						}
7224 
7225 						sibling = sibling.previousSibling;
7226 					}
7227 				}
7228 
7229 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7230 			};
7231 
7232 			try {
7233 				// Find start point
7234 				findEndPoint(true);
7235 
7236 				// Find end point if needed
7237 				if (!collapsed)
7238 					findEndPoint();
7239 			} catch (ex) {
7240 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7241 				// access the nodeValue or other properties of text nodes. This seems to happend when
7242 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7243 				if (ex.number == -2147024809) {
7244 					// Get the current selection
7245 					bookmark = self.getBookmark(2);
7246 
7247 					// Get start element
7248 					tmpRange = ieRange.duplicate();
7249 					tmpRange.collapse(true);
7250 					element = tmpRange.parentElement();
7251 
7252 					// Get end element
7253 					if (!collapsed) {
7254 						tmpRange = ieRange.duplicate();
7255 						tmpRange.collapse(false);
7256 						element2 = tmpRange.parentElement();
7257 						element2.innerHTML = element2.innerHTML;
7258 					}
7259 
7260 					// Remove the broken elements
7261 					element.innerHTML = element.innerHTML;
7262 
7263 					// Restore the selection
7264 					self.moveToBookmark(bookmark);
7265 
7266 					// Since the range has moved we need to re-get it
7267 					ieRange = selection.getRng();
7268 
7269 					// Find start point
7270 					findEndPoint(true);
7271 
7272 					// Find end point if needed
7273 					if (!collapsed)
7274 						findEndPoint();
7275 				} else
7276 					throw ex; // Throw other errors
7277 			}
7278 
7279 			return domRange;
7280 		};
7281 
7282 		this.getBookmark = function(type) {
7283 			var rng = selection.getRng(), start, end, bookmark = {};
7284 
7285 			function getIndexes(node) {
7286 				var parent, root, children, i, indexes = [];
7287 
7288 				parent = node.parentNode;
7289 				root = dom.getRoot().parentNode;
7290 
7291 				while (parent != root && parent.nodeType !== 9) {
7292 					children = parent.children;
7293 
7294 					i = children.length;
7295 					while (i--) {
7296 						if (node === children[i]) {
7297 							indexes.push(i);
7298 							break;
7299 						}
7300 					}
7301 
7302 					node = parent;
7303 					parent = parent.parentNode;
7304 				}
7305 
7306 				return indexes;
7307 			};
7308 
7309 			function getBookmarkEndPoint(start) {
7310 				var position;
7311 
7312 				position = getPosition(rng, start);
7313 				if (position) {
7314 					return {
7315 						position : position.position,
7316 						offset : position.offset,
7317 						indexes : getIndexes(position.node),
7318 						inside : position.inside
7319 					};
7320 				}
7321 			};
7322 
7323 			// Non ubstructive bookmark
7324 			if (type === 2) {
7325 				// Handle text selection
7326 				if (!rng.item) {
7327 					bookmark.start = getBookmarkEndPoint(true);
7328 
7329 					if (!selection.isCollapsed())
7330 						bookmark.end = getBookmarkEndPoint();
7331 				} else
7332 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7333 			}
7334 
7335 			return bookmark;
7336 		};
7337 
7338 		this.moveToBookmark = function(bookmark) {
7339 			var rng, body = dom.doc.body;
7340 
7341 			function resolveIndexes(indexes) {
7342 				var node, i, idx, children;
7343 
7344 				node = dom.getRoot();
7345 				for (i = indexes.length - 1; i >= 0; i--) {
7346 					children = node.children;
7347 					idx = indexes[i];
7348 
7349 					if (idx <= children.length - 1) {
7350 						node = children[idx];
7351 					}
7352 				}
7353 
7354 				return node;
7355 			};
7356 			
7357 			function setBookmarkEndPoint(start) {
7358 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7359 
7360 				if (endPoint) {
7361 					moveLeft = endPoint.position > 0;
7362 
7363 					moveRng = body.createTextRange();
7364 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7365 
7366 					offset = endPoint.offset;
7367 					if (offset !== undef) {
7368 						moveRng.collapse(endPoint.inside || moveLeft);
7369 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7370 					} else
7371 						moveRng.collapse(start);
7372 
7373 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7374 
7375 					if (start)
7376 						rng.collapse(true);
7377 				}
7378 			};
7379 
7380 			if (bookmark.start) {
7381 				if (bookmark.start.ctrl) {
7382 					rng = body.createControlRange();
7383 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7384 					rng.select();
7385 				} else {
7386 					rng = body.createTextRange();
7387 					setBookmarkEndPoint(true);
7388 					setBookmarkEndPoint();
7389 					rng.select();
7390 				}
7391 			}
7392 		};
7393 
7394 		this.addRange = function(rng) {
7395 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7396 
7397 			function setEndPoint(start) {
7398 				var container, offset, marker, tmpRng, nodes;
7399 
7400 				marker = dom.create('a');
7401 				container = start ? startContainer : endContainer;
7402 				offset = start ? startOffset : endOffset;
7403 				tmpRng = ieRng.duplicate();
7404 
7405 				if (container == doc || container == doc.documentElement) {
7406 					container = body;
7407 					offset = 0;
7408 				}
7409 
7410 				if (container.nodeType == 3) {
7411 					container.parentNode.insertBefore(marker, container);
7412 					tmpRng.moveToElementText(marker);
7413 					tmpRng.moveStart('character', offset);
7414 					dom.remove(marker);
7415 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7416 				} else {
7417 					nodes = container.childNodes;
7418 
7419 					if (nodes.length) {
7420 						if (offset >= nodes.length) {
7421 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7422 						} else {
7423 							container.insertBefore(marker, nodes[offset]);
7424 						}
7425 
7426 						tmpRng.moveToElementText(marker);
7427 					} else if (container.canHaveHTML) {
7428 						// Empty node selection for example <div>|</div>
7429 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7430 						container.innerHTML = '<span>\uFEFF</span>';
7431 						marker = container.firstChild;
7432 						tmpRng.moveToElementText(marker);
7433 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7434 					}
7435 
7436 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7437 					dom.remove(marker);
7438 				}
7439 			}
7440 
7441 			// Setup some shorter versions
7442 			startContainer = rng.startContainer;
7443 			startOffset = rng.startOffset;
7444 			endContainer = rng.endContainer;
7445 			endOffset = rng.endOffset;
7446 			ieRng = body.createTextRange();
7447 
7448 			// If single element selection then try making a control selection out of it
7449 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7450 				// Trick to place the caret inside an empty block element like <p></p>
7451 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7452 					if (startContainer.canHaveHTML) {
7453 						// Check if previous sibling is an empty block if it is then we need to render it
7454 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7455 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7456 						sibling = startContainer.previousSibling;
7457 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7458 							sibling.innerHTML = '\uFEFF';
7459 						} else {
7460 							sibling = null;
7461 						}
7462 
7463 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7464 						ieRng.moveToElementText(startContainer.lastChild);
7465 						ieRng.select();
7466 						dom.doc.selection.clear();
7467 						startContainer.innerHTML = '';
7468 
7469 						if (sibling) {
7470 							sibling.innerHTML = '';
7471 						}
7472 						return;
7473 					} else {
7474 						startOffset = dom.nodeIndex(startContainer);
7475 						startContainer = startContainer.parentNode;
7476 					}
7477 				}
7478 
7479 				if (startOffset == endOffset - 1) {
7480 					try {
7481 						ctrlRng = body.createControlRange();
7482 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7483 						ctrlRng.select();
7484 						return;
7485 					} catch (ex) {
7486 						// Ignore
7487 					}
7488 				}
7489 			}
7490 
7491 			// Set start/end point of selection
7492 			setEndPoint(true);
7493 			setEndPoint();
7494 
7495 			// Select the new range and scroll it into view
7496 			ieRng.select();
7497 		};
7498 
7499 		// Expose range method
7500 		this.getRangeAt = getRange;
7501 	};
7502 
7503 	// Expose the selection object
7504 	tinymce.dom.TridentSelection = Selection;
7505 })();
7506 
7507 
7508 /*
7509  * Sizzle CSS Selector Engine
7510  *  Copyright, The Dojo Foundation
7511  *  Released under the MIT, BSD, and GPL Licenses.
7512  *  More information: http://sizzlejs.com/
7513  */
7514 (function(){
7515 
7516 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7517 	expando = "sizcache",
7518 	done = 0,
7519 	toString = Object.prototype.toString,
7520 	hasDuplicate = false,
7521 	baseHasDuplicate = true,
7522 	rBackslash = /\\/g,
7523 	rReturn = /\r\n/g,
7524 	rNonWord = /\W/;
7525 
7526 // Here we check if the JavaScript engine is using some sort of
7527 // optimization where it does not always call our comparision
7528 // function. If that is the case, discard the hasDuplicate value.
7529 //   Thus far that includes Google Chrome.
7530 [0, 0].sort(function() {
7531 	baseHasDuplicate = false;
7532 	return 0;
7533 });
7534 
7535 var Sizzle = function( selector, context, results, seed ) {
7536 	results = results || [];
7537 	context = context || document;
7538 
7539 	var origContext = context;
7540 
7541 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7542 		return [];
7543 	}
7544 
7545 	if ( !selector || typeof selector !== "string" ) {
7546 		return results;
7547 	}
7548 
7549 	var m, set, checkSet, extra, ret, cur, pop, i,
7550 		prune = true,
7551 		contextXML = Sizzle.isXML( context ),
7552 		parts = [],
7553 		soFar = selector;
7554 
7555 	// Reset the position of the chunker regexp (start from head)
7556 	do {
7557 		chunker.exec( "" );
7558 		m = chunker.exec( soFar );
7559 
7560 		if ( m ) {
7561 			soFar = m[3];
7562 
7563 			parts.push( m[1] );
7564 
7565 			if ( m[2] ) {
7566 				extra = m[3];
7567 				break;
7568 			}
7569 		}
7570 	} while ( m );
7571 
7572 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
7573 
7574 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7575 			set = posProcess( parts[0] + parts[1], context, seed );
7576 
7577 		} else {
7578 			set = Expr.relative[ parts[0] ] ?
7579 				[ context ] :
7580 				Sizzle( parts.shift(), context );
7581 
7582 			while ( parts.length ) {
7583 				selector = parts.shift();
7584 
7585 				if ( Expr.relative[ selector ] ) {
7586 					selector += parts.shift();
7587 				}
7588 
7589 				set = posProcess( selector, set, seed );
7590 			}
7591 		}
7592 
7593 	} else {
7594 		// Take a shortcut and set the context if the root selector is an ID
7595 		// (but not if it'll be faster if the inner selector is an ID)
7596 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7597 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7598 
7599 			ret = Sizzle.find( parts.shift(), context, contextXML );
7600 			context = ret.expr ?
7601 				Sizzle.filter( ret.expr, ret.set )[0] :
7602 				ret.set[0];
7603 		}
7604 
7605 		if ( context ) {
7606 			ret = seed ?
7607 				{ expr: parts.pop(), set: makeArray(seed) } :
7608 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7609 
7610 			set = ret.expr ?
7611 				Sizzle.filter( ret.expr, ret.set ) :
7612 				ret.set;
7613 
7614 			if ( parts.length > 0 ) {
7615 				checkSet = makeArray( set );
7616 
7617 			} else {
7618 				prune = false;
7619 			}
7620 
7621 			while ( parts.length ) {
7622 				cur = parts.pop();
7623 				pop = cur;
7624 
7625 				if ( !Expr.relative[ cur ] ) {
7626 					cur = "";
7627 				} else {
7628 					pop = parts.pop();
7629 				}
7630 
7631 				if ( pop == null ) {
7632 					pop = context;
7633 				}
7634 
7635 				Expr.relative[ cur ]( checkSet, pop, contextXML );
7636 			}
7637 
7638 		} else {
7639 			checkSet = parts = [];
7640 		}
7641 	}
7642 
7643 	if ( !checkSet ) {
7644 		checkSet = set;
7645 	}
7646 
7647 	if ( !checkSet ) {
7648 		Sizzle.error( cur || selector );
7649 	}
7650 
7651 	if ( toString.call(checkSet) === "[object Array]" ) {
7652 		if ( !prune ) {
7653 			results.push.apply( results, checkSet );
7654 
7655 		} else if ( context && context.nodeType === 1 ) {
7656 			for ( i = 0; checkSet[i] != null; i++ ) {
7657 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7658 					results.push( set[i] );
7659 				}
7660 			}
7661 
7662 		} else {
7663 			for ( i = 0; checkSet[i] != null; i++ ) {
7664 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7665 					results.push( set[i] );
7666 				}
7667 			}
7668 		}
7669 
7670 	} else {
7671 		makeArray( checkSet, results );
7672 	}
7673 
7674 	if ( extra ) {
7675 		Sizzle( extra, origContext, results, seed );
7676 		Sizzle.uniqueSort( results );
7677 	}
7678 
7679 	return results;
7680 };
7681 
7682 Sizzle.uniqueSort = function( results ) {
7683 	if ( sortOrder ) {
7684 		hasDuplicate = baseHasDuplicate;
7685 		results.sort( sortOrder );
7686 
7687 		if ( hasDuplicate ) {
7688 			for ( var i = 1; i < results.length; i++ ) {
7689 				if ( results[i] === results[ i - 1 ] ) {
7690 					results.splice( i--, 1 );
7691 				}
7692 			}
7693 		}
7694 	}
7695 
7696 	return results;
7697 };
7698 
7699 Sizzle.matches = function( expr, set ) {
7700 	return Sizzle( expr, null, null, set );
7701 };
7702 
7703 Sizzle.matchesSelector = function( node, expr ) {
7704 	return Sizzle( expr, null, null, [node] ).length > 0;
7705 };
7706 
7707 Sizzle.find = function( expr, context, isXML ) {
7708 	var set, i, len, match, type, left;
7709 
7710 	if ( !expr ) {
7711 		return [];
7712 	}
7713 
7714 	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7715 		type = Expr.order[i];
7716 
7717 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7718 			left = match[1];
7719 			match.splice( 1, 1 );
7720 
7721 			if ( left.substr( left.length - 1 ) !== "\\" ) {
7722 				match[1] = (match[1] || "").replace( rBackslash, "" );
7723 				set = Expr.find[ type ]( match, context, isXML );
7724 
7725 				if ( set != null ) {
7726 					expr = expr.replace( Expr.match[ type ], "" );
7727 					break;
7728 				}
7729 			}
7730 		}
7731 	}
7732 
7733 	if ( !set ) {
7734 		set = typeof context.getElementsByTagName !== "undefined" ?
7735 			context.getElementsByTagName( "*" ) :
7736 			[];
7737 	}
7738 
7739 	return { set: set, expr: expr };
7740 };
7741 
7742 Sizzle.filter = function( expr, set, inplace, not ) {
7743 	var match, anyFound,
7744 		type, found, item, filter, left,
7745 		i, pass,
7746 		old = expr,
7747 		result = [],
7748 		curLoop = set,
7749 		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
7750 
7751 	while ( expr && set.length ) {
7752 		for ( type in Expr.filter ) {
7753 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
7754 				filter = Expr.filter[ type ];
7755 				left = match[1];
7756 
7757 				anyFound = false;
7758 
7759 				match.splice(1,1);
7760 
7761 				if ( left.substr( left.length - 1 ) === "\\" ) {
7762 					continue;
7763 				}
7764 
7765 				if ( curLoop === result ) {
7766 					result = [];
7767 				}
7768 
7769 				if ( Expr.preFilter[ type ] ) {
7770 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
7771 
7772 					if ( !match ) {
7773 						anyFound = found = true;
7774 
7775 					} else if ( match === true ) {
7776 						continue;
7777 					}
7778 				}
7779 
7780 				if ( match ) {
7781 					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
7782 						if ( item ) {
7783 							found = filter( item, match, i, curLoop );
7784 							pass = not ^ found;
7785 
7786 							if ( inplace && found != null ) {
7787 								if ( pass ) {
7788 									anyFound = true;
7789 
7790 								} else {
7791 									curLoop[i] = false;
7792 								}
7793 
7794 							} else if ( pass ) {
7795 								result.push( item );
7796 								anyFound = true;
7797 							}
7798 						}
7799 					}
7800 				}
7801 
7802 				if ( found !== undefined ) {
7803 					if ( !inplace ) {
7804 						curLoop = result;
7805 					}
7806 
7807 					expr = expr.replace( Expr.match[ type ], "" );
7808 
7809 					if ( !anyFound ) {
7810 						return [];
7811 					}
7812 
7813 					break;
7814 				}
7815 			}
7816 		}
7817 
7818 		// Improper expression
7819 		if ( expr === old ) {
7820 			if ( anyFound == null ) {
7821 				Sizzle.error( expr );
7822 
7823 			} else {
7824 				break;
7825 			}
7826 		}
7827 
7828 		old = expr;
7829 	}
7830 
7831 	return curLoop;
7832 };
7833 
7834 Sizzle.error = function( msg ) {
7835 	throw new Error( "Syntax error, unrecognized expression: " + msg );
7836 };
7837 
7838 var getText = Sizzle.getText = function( elem ) {
7839     var i, node,
7840 		nodeType = elem.nodeType,
7841 		ret = "";
7842 
7843 	if ( nodeType ) {
7844 		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
7845 			// Use textContent || innerText for elements
7846 			if ( typeof elem.textContent === 'string' ) {
7847 				return elem.textContent;
7848 			} else if ( typeof elem.innerText === 'string' ) {
7849 				// Replace IE's carriage returns
7850 				return elem.innerText.replace( rReturn, '' );
7851 			} else {
7852 				// Traverse it's children
7853 				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
7854 					ret += getText( elem );
7855 				}
7856 			}
7857 		} else if ( nodeType === 3 || nodeType === 4 ) {
7858 			return elem.nodeValue;
7859 		}
7860 	} else {
7861 
7862 		// If no nodeType, this is expected to be an array
7863 		for ( i = 0; (node = elem[i]); i++ ) {
7864 			// Do not traverse comment nodes
7865 			if ( node.nodeType !== 8 ) {
7866 				ret += getText( node );
7867 			}
7868 		}
7869 	}
7870 	return ret;
7871 };
7872 
7873 var Expr = Sizzle.selectors = {
7874 	order: [ "ID", "NAME", "TAG" ],
7875 
7876 	match: {
7877 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7878 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
7879 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
7880 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
7881 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
7882 		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
7883 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
7884 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
7885 	},
7886 
7887 	leftMatch: {},
7888 
7889 	attrMap: {
7890 		"class": "className",
7891 		"for": "htmlFor"
7892 	},
7893 
7894 	attrHandle: {
7895 		href: function( elem ) {
7896 			return elem.getAttribute( "href" );
7897 		},
7898 		type: function( elem ) {
7899 			return elem.getAttribute( "type" );
7900 		}
7901 	},
7902 
7903 	relative: {
7904 		"+": function(checkSet, part){
7905 			var isPartStr = typeof part === "string",
7906 				isTag = isPartStr && !rNonWord.test( part ),
7907 				isPartStrNotTag = isPartStr && !isTag;
7908 
7909 			if ( isTag ) {
7910 				part = part.toLowerCase();
7911 			}
7912 
7913 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
7914 				if ( (elem = checkSet[i]) ) {
7915 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
7916 
7917 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
7918 						elem || false :
7919 						elem === part;
7920 				}
7921 			}
7922 
7923 			if ( isPartStrNotTag ) {
7924 				Sizzle.filter( part, checkSet, true );
7925 			}
7926 		},
7927 
7928 		">": function( checkSet, part ) {
7929 			var elem,
7930 				isPartStr = typeof part === "string",
7931 				i = 0,
7932 				l = checkSet.length;
7933 
7934 			if ( isPartStr && !rNonWord.test( part ) ) {
7935 				part = part.toLowerCase();
7936 
7937 				for ( ; i < l; i++ ) {
7938 					elem = checkSet[i];
7939 
7940 					if ( elem ) {
7941 						var parent = elem.parentNode;
7942 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
7943 					}
7944 				}
7945 
7946 			} else {
7947 				for ( ; i < l; i++ ) {
7948 					elem = checkSet[i];
7949 
7950 					if ( elem ) {
7951 						checkSet[i] = isPartStr ?
7952 							elem.parentNode :
7953 							elem.parentNode === part;
7954 					}
7955 				}
7956 
7957 				if ( isPartStr ) {
7958 					Sizzle.filter( part, checkSet, true );
7959 				}
7960 			}
7961 		},
7962 
7963 		"": function(checkSet, part, isXML){
7964 			var nodeCheck,
7965 				doneName = done++,
7966 				checkFn = dirCheck;
7967 
7968 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7969 				part = part.toLowerCase();
7970 				nodeCheck = part;
7971 				checkFn = dirNodeCheck;
7972 			}
7973 
7974 			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
7975 		},
7976 
7977 		"~": function( checkSet, part, isXML ) {
7978 			var nodeCheck,
7979 				doneName = done++,
7980 				checkFn = dirCheck;
7981 
7982 			if ( typeof part === "string" && !rNonWord.test( part ) ) {
7983 				part = part.toLowerCase();
7984 				nodeCheck = part;
7985 				checkFn = dirNodeCheck;
7986 			}
7987 
7988 			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
7989 		}
7990 	},
7991 
7992 	find: {
7993 		ID: function( match, context, isXML ) {
7994 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
7995 				var m = context.getElementById(match[1]);
7996 				// Check parentNode to catch when Blackberry 4.6 returns
7997 				// nodes that are no longer in the document #6963
7998 				return m && m.parentNode ? [m] : [];
7999 			}
8000 		},
8001 
8002 		NAME: function( match, context ) {
8003 			if ( typeof context.getElementsByName !== "undefined" ) {
8004 				var ret = [],
8005 					results = context.getElementsByName( match[1] );
8006 
8007 				for ( var i = 0, l = results.length; i < l; i++ ) {
8008 					if ( results[i].getAttribute("name") === match[1] ) {
8009 						ret.push( results[i] );
8010 					}
8011 				}
8012 
8013 				return ret.length === 0 ? null : ret;
8014 			}
8015 		},
8016 
8017 		TAG: function( match, context ) {
8018 			if ( typeof context.getElementsByTagName !== "undefined" ) {
8019 				return context.getElementsByTagName( match[1] );
8020 			}
8021 		}
8022 	},
8023 	preFilter: {
8024 		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8025 			match = " " + match[1].replace( rBackslash, "" ) + " ";
8026 
8027 			if ( isXML ) {
8028 				return match;
8029 			}
8030 
8031 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8032 				if ( elem ) {
8033 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8034 						if ( !inplace ) {
8035 							result.push( elem );
8036 						}
8037 
8038 					} else if ( inplace ) {
8039 						curLoop[i] = false;
8040 					}
8041 				}
8042 			}
8043 
8044 			return false;
8045 		},
8046 
8047 		ID: function( match ) {
8048 			return match[1].replace( rBackslash, "" );
8049 		},
8050 
8051 		TAG: function( match, curLoop ) {
8052 			return match[1].replace( rBackslash, "" ).toLowerCase();
8053 		},
8054 
8055 		CHILD: function( match ) {
8056 			if ( match[1] === "nth" ) {
8057 				if ( !match[2] ) {
8058 					Sizzle.error( match[0] );
8059 				}
8060 
8061 				match[2] = match[2].replace(/^\+|\s*/g, '');
8062 
8063 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8064 				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8065 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8066 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8067 
8068 				// calculate the numbers (first)n+(last) including if they are negative
8069 				match[2] = (test[1] + (test[2] || 1)) - 0;
8070 				match[3] = test[3] - 0;
8071 			}
8072 			else if ( match[2] ) {
8073 				Sizzle.error( match[0] );
8074 			}
8075 
8076 			// TODO: Move to normal caching system
8077 			match[0] = done++;
8078 
8079 			return match;
8080 		},
8081 
8082 		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8083 			var name = match[1] = match[1].replace( rBackslash, "" );
8084 
8085 			if ( !isXML && Expr.attrMap[name] ) {
8086 				match[1] = Expr.attrMap[name];
8087 			}
8088 
8089 			// Handle if an un-quoted value was used
8090 			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8091 
8092 			if ( match[2] === "~=" ) {
8093 				match[4] = " " + match[4] + " ";
8094 			}
8095 
8096 			return match;
8097 		},
8098 
8099 		PSEUDO: function( match, curLoop, inplace, result, not ) {
8100 			if ( match[1] === "not" ) {
8101 				// If we're dealing with a complex expression, or a simple one
8102 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8103 					match[3] = Sizzle(match[3], null, null, curLoop);
8104 
8105 				} else {
8106 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8107 
8108 					if ( !inplace ) {
8109 						result.push.apply( result, ret );
8110 					}
8111 
8112 					return false;
8113 				}
8114 
8115 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8116 				return true;
8117 			}
8118 
8119 			return match;
8120 		},
8121 
8122 		POS: function( match ) {
8123 			match.unshift( true );
8124 
8125 			return match;
8126 		}
8127 	},
8128 
8129 	filters: {
8130 		enabled: function( elem ) {
8131 			return elem.disabled === false && elem.type !== "hidden";
8132 		},
8133 
8134 		disabled: function( elem ) {
8135 			return elem.disabled === true;
8136 		},
8137 
8138 		checked: function( elem ) {
8139 			return elem.checked === true;
8140 		},
8141 
8142 		selected: function( elem ) {
8143 			// Accessing this property makes selected-by-default
8144 			// options in Safari work properly
8145 			if ( elem.parentNode ) {
8146 				elem.parentNode.selectedIndex;
8147 			}
8148 
8149 			return elem.selected === true;
8150 		},
8151 
8152 		parent: function( elem ) {
8153 			return !!elem.firstChild;
8154 		},
8155 
8156 		empty: function( elem ) {
8157 			return !elem.firstChild;
8158 		},
8159 
8160 		has: function( elem, i, match ) {
8161 			return !!Sizzle( match[3], elem ).length;
8162 		},
8163 
8164 		header: function( elem ) {
8165 			return (/h\d/i).test( elem.nodeName );
8166 		},
8167 
8168 		text: function( elem ) {
8169 			var attr = elem.getAttribute( "type" ), type = elem.type;
8170 			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8171 			// use getAttribute instead to test this case
8172 			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8173 		},
8174 
8175 		radio: function( elem ) {
8176 			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8177 		},
8178 
8179 		checkbox: function( elem ) {
8180 			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8181 		},
8182 
8183 		file: function( elem ) {
8184 			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8185 		},
8186 
8187 		password: function( elem ) {
8188 			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8189 		},
8190 
8191 		submit: function( elem ) {
8192 			var name = elem.nodeName.toLowerCase();
8193 			return (name === "input" || name === "button") && "submit" === elem.type;
8194 		},
8195 
8196 		image: function( elem ) {
8197 			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8198 		},
8199 
8200 		reset: function( elem ) {
8201 			var name = elem.nodeName.toLowerCase();
8202 			return (name === "input" || name === "button") && "reset" === elem.type;
8203 		},
8204 
8205 		button: function( elem ) {
8206 			var name = elem.nodeName.toLowerCase();
8207 			return name === "input" && "button" === elem.type || name === "button";
8208 		},
8209 
8210 		input: function( elem ) {
8211 			return (/input|select|textarea|button/i).test( elem.nodeName );
8212 		},
8213 
8214 		focus: function( elem ) {
8215 			return elem === elem.ownerDocument.activeElement;
8216 		}
8217 	},
8218 	setFilters: {
8219 		first: function( elem, i ) {
8220 			return i === 0;
8221 		},
8222 
8223 		last: function( elem, i, match, array ) {
8224 			return i === array.length - 1;
8225 		},
8226 
8227 		even: function( elem, i ) {
8228 			return i % 2 === 0;
8229 		},
8230 
8231 		odd: function( elem, i ) {
8232 			return i % 2 === 1;
8233 		},
8234 
8235 		lt: function( elem, i, match ) {
8236 			return i < match[3] - 0;
8237 		},
8238 
8239 		gt: function( elem, i, match ) {
8240 			return i > match[3] - 0;
8241 		},
8242 
8243 		nth: function( elem, i, match ) {
8244 			return match[3] - 0 === i;
8245 		},
8246 
8247 		eq: function( elem, i, match ) {
8248 			return match[3] - 0 === i;
8249 		}
8250 	},
8251 	filter: {
8252 		PSEUDO: function( elem, match, i, array ) {
8253 			var name = match[1],
8254 				filter = Expr.filters[ name ];
8255 
8256 			if ( filter ) {
8257 				return filter( elem, i, match, array );
8258 
8259 			} else if ( name === "contains" ) {
8260 				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8261 
8262 			} else if ( name === "not" ) {
8263 				var not = match[3];
8264 
8265 				for ( var j = 0, l = not.length; j < l; j++ ) {
8266 					if ( not[j] === elem ) {
8267 						return false;
8268 					}
8269 				}
8270 
8271 				return true;
8272 
8273 			} else {
8274 				Sizzle.error( name );
8275 			}
8276 		},
8277 
8278 		CHILD: function( elem, match ) {
8279 			var first, last,
8280 				doneName, parent, cache,
8281 				count, diff,
8282 				type = match[1],
8283 				node = elem;
8284 
8285 			switch ( type ) {
8286 				case "only":
8287 				case "first":
8288 					while ( (node = node.previousSibling) ) {
8289 						if ( node.nodeType === 1 ) {
8290 							return false;
8291 						}
8292 					}
8293 
8294 					if ( type === "first" ) {
8295 						return true;
8296 					}
8297 
8298 					node = elem;
8299 
8300 					/* falls through */
8301 				case "last":
8302 					while ( (node = node.nextSibling) ) {
8303 						if ( node.nodeType === 1 ) {
8304 							return false;
8305 						}
8306 					}
8307 
8308 					return true;
8309 
8310 				case "nth":
8311 					first = match[2];
8312 					last = match[3];
8313 
8314 					if ( first === 1 && last === 0 ) {
8315 						return true;
8316 					}
8317 
8318 					doneName = match[0];
8319 					parent = elem.parentNode;
8320 
8321 					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8322 						count = 0;
8323 
8324 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
8325 							if ( node.nodeType === 1 ) {
8326 								node.nodeIndex = ++count;
8327 							}
8328 						}
8329 
8330 						parent[ expando ] = doneName;
8331 					}
8332 
8333 					diff = elem.nodeIndex - last;
8334 
8335 					if ( first === 0 ) {
8336 						return diff === 0;
8337 
8338 					} else {
8339 						return ( diff % first === 0 && diff / first >= 0 );
8340 					}
8341 			}
8342 		},
8343 
8344 		ID: function( elem, match ) {
8345 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
8346 		},
8347 
8348 		TAG: function( elem, match ) {
8349 			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8350 		},
8351 
8352 		CLASS: function( elem, match ) {
8353 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
8354 				.indexOf( match ) > -1;
8355 		},
8356 
8357 		ATTR: function( elem, match ) {
8358 			var name = match[1],
8359 				result = Sizzle.attr ?
8360 					Sizzle.attr( elem, name ) :
8361 					Expr.attrHandle[ name ] ?
8362 					Expr.attrHandle[ name ]( elem ) :
8363 					elem[ name ] != null ?
8364 						elem[ name ] :
8365 						elem.getAttribute( name ),
8366 				value = result + "",
8367 				type = match[2],
8368 				check = match[4];
8369 
8370 			return result == null ?
8371 				type === "!=" :
8372 				!type && Sizzle.attr ?
8373 				result != null :
8374 				type === "=" ?
8375 				value === check :
8376 				type === "*=" ?
8377 				value.indexOf(check) >= 0 :
8378 				type === "~=" ?
8379 				(" " + value + " ").indexOf(check) >= 0 :
8380 				!check ?
8381 				value && result !== false :
8382 				type === "!=" ?
8383 				value !== check :
8384 				type === "^=" ?
8385 				value.indexOf(check) === 0 :
8386 				type === "$=" ?
8387 				value.substr(value.length - check.length) === check :
8388 				type === "|=" ?
8389 				value === check || value.substr(0, check.length + 1) === check + "-" :
8390 				false;
8391 		},
8392 
8393 		POS: function( elem, match, i, array ) {
8394 			var name = match[2],
8395 				filter = Expr.setFilters[ name ];
8396 
8397 			if ( filter ) {
8398 				return filter( elem, i, match, array );
8399 			}
8400 		}
8401 	}
8402 };
8403 
8404 var origPOS = Expr.match.POS,
8405 	fescape = function(all, num){
8406 		return "\\" + (num - 0 + 1);
8407 	};
8408 
8409 for ( var type in Expr.match ) {
8410 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8411 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8412 }
8413 // Expose origPOS
8414 // "global" as in regardless of relation to brackets/parens
8415 Expr.match.globalPOS = origPOS;
8416 
8417 var makeArray = function( array, results ) {
8418 	array = Array.prototype.slice.call( array, 0 );
8419 
8420 	if ( results ) {
8421 		results.push.apply( results, array );
8422 		return results;
8423 	}
8424 
8425 	return array;
8426 };
8427 
8428 // Perform a simple check to determine if the browser is capable of
8429 // converting a NodeList to an array using builtin methods.
8430 // Also verifies that the returned array holds DOM nodes
8431 // (which is not the case in the Blackberry browser)
8432 try {
8433 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8434 
8435 // Provide a fallback method if it does not work
8436 } catch( e ) {
8437 	makeArray = function( array, results ) {
8438 		var i = 0,
8439 			ret = results || [];
8440 
8441 		if ( toString.call(array) === "[object Array]" ) {
8442 			Array.prototype.push.apply( ret, array );
8443 
8444 		} else {
8445 			if ( typeof array.length === "number" ) {
8446 				for ( var l = array.length; i < l; i++ ) {
8447 					ret.push( array[i] );
8448 				}
8449 
8450 			} else {
8451 				for ( ; array[i]; i++ ) {
8452 					ret.push( array[i] );
8453 				}
8454 			}
8455 		}
8456 
8457 		return ret;
8458 	};
8459 }
8460 
8461 var sortOrder, siblingCheck;
8462 
8463 if ( document.documentElement.compareDocumentPosition ) {
8464 	sortOrder = function( a, b ) {
8465 		if ( a === b ) {
8466 			hasDuplicate = true;
8467 			return 0;
8468 		}
8469 
8470 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8471 			return a.compareDocumentPosition ? -1 : 1;
8472 		}
8473 
8474 		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8475 	};
8476 
8477 } else {
8478 	sortOrder = function( a, b ) {
8479 		// The nodes are identical, we can exit early
8480 		if ( a === b ) {
8481 			hasDuplicate = true;
8482 			return 0;
8483 
8484 		// Fallback to using sourceIndex (in IE) if it's available on both nodes
8485 		} else if ( a.sourceIndex && b.sourceIndex ) {
8486 			return a.sourceIndex - b.sourceIndex;
8487 		}
8488 
8489 		var al, bl,
8490 			ap = [],
8491 			bp = [],
8492 			aup = a.parentNode,
8493 			bup = b.parentNode,
8494 			cur = aup;
8495 
8496 		// If the nodes are siblings (or identical) we can do a quick check
8497 		if ( aup === bup ) {
8498 			return siblingCheck( a, b );
8499 
8500 		// If no parents were found then the nodes are disconnected
8501 		} else if ( !aup ) {
8502 			return -1;
8503 
8504 		} else if ( !bup ) {
8505 			return 1;
8506 		}
8507 
8508 		// Otherwise they're somewhere else in the tree so we need
8509 		// to build up a full list of the parentNodes for comparison
8510 		while ( cur ) {
8511 			ap.unshift( cur );
8512 			cur = cur.parentNode;
8513 		}
8514 
8515 		cur = bup;
8516 
8517 		while ( cur ) {
8518 			bp.unshift( cur );
8519 			cur = cur.parentNode;
8520 		}
8521 
8522 		al = ap.length;
8523 		bl = bp.length;
8524 
8525 		// Start walking down the tree looking for a discrepancy
8526 		for ( var i = 0; i < al && i < bl; i++ ) {
8527 			if ( ap[i] !== bp[i] ) {
8528 				return siblingCheck( ap[i], bp[i] );
8529 			}
8530 		}
8531 
8532 		// We ended someplace up the tree so do a sibling check
8533 		return i === al ?
8534 			siblingCheck( a, bp[i], -1 ) :
8535 			siblingCheck( ap[i], b, 1 );
8536 	};
8537 
8538 	siblingCheck = function( a, b, ret ) {
8539 		if ( a === b ) {
8540 			return ret;
8541 		}
8542 
8543 		var cur = a.nextSibling;
8544 
8545 		while ( cur ) {
8546 			if ( cur === b ) {
8547 				return -1;
8548 			}
8549 
8550 			cur = cur.nextSibling;
8551 		}
8552 
8553 		return 1;
8554 	};
8555 }
8556 
8557 // Check to see if the browser returns elements by name when
8558 // querying by getElementById (and provide a workaround)
8559 (function(){
8560 	// We're going to inject a fake input element with a specified name
8561 	var form = document.createElement("div"),
8562 		id = "script" + (new Date()).getTime(),
8563 		root = document.documentElement;
8564 
8565 	form.innerHTML = "<a name='" + id + "'/>";
8566 
8567 	// Inject it into the root element, check its status, and remove it quickly
8568 	root.insertBefore( form, root.firstChild );
8569 
8570 	// The workaround has to do additional checks after a getElementById
8571 	// Which slows things down for other browsers (hence the branching)
8572 	if ( document.getElementById( id ) ) {
8573 		Expr.find.ID = function( match, context, isXML ) {
8574 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
8575 				var m = context.getElementById(match[1]);
8576 
8577 				return m ?
8578 					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8579 						[m] :
8580 						undefined :
8581 					[];
8582 			}
8583 		};
8584 
8585 		Expr.filter.ID = function( elem, match ) {
8586 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8587 
8588 			return elem.nodeType === 1 && node && node.nodeValue === match;
8589 		};
8590 	}
8591 
8592 	root.removeChild( form );
8593 
8594 	// release memory in IE
8595 	root = form = null;
8596 })();
8597 
8598 (function(){
8599 	// Check to see if the browser returns only elements
8600 	// when doing getElementsByTagName("*")
8601 
8602 	// Create a fake element
8603 	var div = document.createElement("div");
8604 	div.appendChild( document.createComment("") );
8605 
8606 	// Make sure no comments are found
8607 	if ( div.getElementsByTagName("*").length > 0 ) {
8608 		Expr.find.TAG = function( match, context ) {
8609 			var results = context.getElementsByTagName( match[1] );
8610 
8611 			// Filter out possible comments
8612 			if ( match[1] === "*" ) {
8613 				var tmp = [];
8614 
8615 				for ( var i = 0; results[i]; i++ ) {
8616 					if ( results[i].nodeType === 1 ) {
8617 						tmp.push( results[i] );
8618 					}
8619 				}
8620 
8621 				results = tmp;
8622 			}
8623 
8624 			return results;
8625 		};
8626 	}
8627 
8628 	// Check to see if an attribute returns normalized href attributes
8629 	div.innerHTML = "<a href='#'></a>";
8630 
8631 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8632 			div.firstChild.getAttribute("href") !== "#" ) {
8633 
8634 		Expr.attrHandle.href = function( elem ) {
8635 			return elem.getAttribute( "href", 2 );
8636 		};
8637 	}
8638 
8639 	// release memory in IE
8640 	div = null;
8641 })();
8642 
8643 if ( document.querySelectorAll ) {
8644 	(function(){
8645 		var oldSizzle = Sizzle,
8646 			div = document.createElement("div"),
8647 			id = "__sizzle__";
8648 
8649 		div.innerHTML = "<p class='TEST'></p>";
8650 
8651 		// Safari can't handle uppercase or unicode characters when
8652 		// in quirks mode.
8653 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8654 			return;
8655 		}
8656 
8657 		Sizzle = function( query, context, extra, seed ) {
8658 			context = context || document;
8659 
8660 			// Only use querySelectorAll on non-XML documents
8661 			// (ID selectors don't work in non-HTML documents)
8662 			if ( !seed && !Sizzle.isXML(context) ) {
8663 				// See if we find a selector to speed up
8664 				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8665 
8666 				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8667 					// Speed-up: Sizzle("TAG")
8668 					if ( match[1] ) {
8669 						return makeArray( context.getElementsByTagName( query ), extra );
8670 
8671 					// Speed-up: Sizzle(".CLASS")
8672 					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8673 						return makeArray( context.getElementsByClassName( match[2] ), extra );
8674 					}
8675 				}
8676 
8677 				if ( context.nodeType === 9 ) {
8678 					// Speed-up: Sizzle("body")
8679 					// The body element only exists once, optimize finding it
8680 					if ( query === "body" && context.body ) {
8681 						return makeArray( [ context.body ], extra );
8682 
8683 					// Speed-up: Sizzle("#ID")
8684 					} else if ( match && match[3] ) {
8685 						var elem = context.getElementById( match[3] );
8686 
8687 						// Check parentNode to catch when Blackberry 4.6 returns
8688 						// nodes that are no longer in the document #6963
8689 						if ( elem && elem.parentNode ) {
8690 							// Handle the case where IE and Opera return items
8691 							// by name instead of ID
8692 							if ( elem.id === match[3] ) {
8693 								return makeArray( [ elem ], extra );
8694 							}
8695 
8696 						} else {
8697 							return makeArray( [], extra );
8698 						}
8699 					}
8700 
8701 					try {
8702 						return makeArray( context.querySelectorAll(query), extra );
8703 					} catch(qsaError) {}
8704 
8705 				// qSA works strangely on Element-rooted queries
8706 				// We can work around this by specifying an extra ID on the root
8707 				// and working up from there (Thanks to Andrew Dupont for the technique)
8708 				// IE 8 doesn't work on object elements
8709 				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8710 					var oldContext = context,
8711 						old = context.getAttribute( "id" ),
8712 						nid = old || id,
8713 						hasParent = context.parentNode,
8714 						relativeHierarchySelector = /^\s*[+~]/.test( query );
8715 
8716 					if ( !old ) {
8717 						context.setAttribute( "id", nid );
8718 					} else {
8719 						nid = nid.replace( /'/g, "\\$&" );
8720 					}
8721 					if ( relativeHierarchySelector && hasParent ) {
8722 						context = context.parentNode;
8723 					}
8724 
8725 					try {
8726 						if ( !relativeHierarchySelector || hasParent ) {
8727 							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8728 						}
8729 
8730 					} catch(pseudoError) {
8731 					} finally {
8732 						if ( !old ) {
8733 							oldContext.removeAttribute( "id" );
8734 						}
8735 					}
8736 				}
8737 			}
8738 
8739 			return oldSizzle(query, context, extra, seed);
8740 		};
8741 
8742 		for ( var prop in oldSizzle ) {
8743 			Sizzle[ prop ] = oldSizzle[ prop ];
8744 		}
8745 
8746 		// release memory in IE
8747 		div = null;
8748 	})();
8749 }
8750 
8751 (function(){
8752 	var html = document.documentElement,
8753 		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
8754 
8755 	if ( matches ) {
8756 		// Check to see if it's possible to do matchesSelector
8757 		// on a disconnected node (IE 9 fails this)
8758 		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
8759 			pseudoWorks = false;
8760 
8761 		try {
8762 			// This should fail with an exception
8763 			// Gecko does not error, returns false instead
8764 			matches.call( document.documentElement, "[test!='']:sizzle" );
8765 
8766 		} catch( pseudoError ) {
8767 			pseudoWorks = true;
8768 		}
8769 
8770 		Sizzle.matchesSelector = function( node, expr ) {
8771 			// Make sure that attribute selectors are quoted
8772 			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
8773 
8774 			if ( !Sizzle.isXML( node ) ) {
8775 				try {
8776 					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
8777 						var ret = matches.call( node, expr );
8778 
8779 						// IE 9's matchesSelector returns false on disconnected nodes
8780 						if ( ret || !disconnectedMatch ||
8781 								// As well, disconnected nodes are said to be in a document
8782 								// fragment in IE 9, so check for that
8783 								node.document && node.document.nodeType !== 11 ) {
8784 							return ret;
8785 						}
8786 					}
8787 				} catch(e) {}
8788 			}
8789 
8790 			return Sizzle(expr, null, null, [node]).length > 0;
8791 		};
8792 	}
8793 })();
8794 
8795 (function(){
8796 	var div = document.createElement("div");
8797 
8798 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
8799 
8800 	// Opera can't find a second classname (in 9.6)
8801 	// Also, make sure that getElementsByClassName actually exists
8802 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
8803 		return;
8804 	}
8805 
8806 	// Safari caches class attributes, doesn't catch changes (in 3.2)
8807 	div.lastChild.className = "e";
8808 
8809 	if ( div.getElementsByClassName("e").length === 1 ) {
8810 		return;
8811 	}
8812 
8813 	Expr.order.splice(1, 0, "CLASS");
8814 	Expr.find.CLASS = function( match, context, isXML ) {
8815 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
8816 			return context.getElementsByClassName(match[1]);
8817 		}
8818 	};
8819 
8820 	// release memory in IE
8821 	div = null;
8822 })();
8823 
8824 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8825 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8826 		var elem = checkSet[i];
8827 
8828 		if ( elem ) {
8829 			var match = false;
8830 
8831 			elem = elem[dir];
8832 
8833 			while ( elem ) {
8834 				if ( elem[ expando ] === doneName ) {
8835 					match = checkSet[elem.sizset];
8836 					break;
8837 				}
8838 
8839 				if ( elem.nodeType === 1 && !isXML ){
8840 					elem[ expando ] = doneName;
8841 					elem.sizset = i;
8842 				}
8843 
8844 				if ( elem.nodeName.toLowerCase() === cur ) {
8845 					match = elem;
8846 					break;
8847 				}
8848 
8849 				elem = elem[dir];
8850 			}
8851 
8852 			checkSet[i] = match;
8853 		}
8854 	}
8855 }
8856 
8857 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
8858 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
8859 		var elem = checkSet[i];
8860 
8861 		if ( elem ) {
8862 			var match = false;
8863 
8864 			elem = elem[dir];
8865 
8866 			while ( elem ) {
8867 				if ( elem[ expando ] === doneName ) {
8868 					match = checkSet[elem.sizset];
8869 					break;
8870 				}
8871 
8872 				if ( elem.nodeType === 1 ) {
8873 					if ( !isXML ) {
8874 						elem[ expando ] = doneName;
8875 						elem.sizset = i;
8876 					}
8877 
8878 					if ( typeof cur !== "string" ) {
8879 						if ( elem === cur ) {
8880 							match = true;
8881 							break;
8882 						}
8883 
8884 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
8885 						match = elem;
8886 						break;
8887 					}
8888 				}
8889 
8890 				elem = elem[dir];
8891 			}
8892 
8893 			checkSet[i] = match;
8894 		}
8895 	}
8896 }
8897 
8898 if ( document.documentElement.contains ) {
8899 	Sizzle.contains = function( a, b ) {
8900 		return a !== b && (a.contains ? a.contains(b) : true);
8901 	};
8902 
8903 } else if ( document.documentElement.compareDocumentPosition ) {
8904 	Sizzle.contains = function( a, b ) {
8905 		return !!(a.compareDocumentPosition(b) & 16);
8906 	};
8907 
8908 } else {
8909 	Sizzle.contains = function() {
8910 		return false;
8911 	};
8912 }
8913 
8914 Sizzle.isXML = function( elem ) {
8915 	// documentElement is verified for cases where it doesn't yet exist
8916 	// (such as loading iframes in IE - #4833)
8917 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
8918 
8919 	return documentElement ? documentElement.nodeName !== "HTML" : false;
8920 };
8921 
8922 var posProcess = function( selector, context, seed ) {
8923 	var match,
8924 		tmpSet = [],
8925 		later = "",
8926 		root = context.nodeType ? [context] : context;
8927 
8928 	// Position selectors must be done after the filter
8929 	// And so must :not(positional) so we move all PSEUDOs to the end
8930 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
8931 		later += match[0];
8932 		selector = selector.replace( Expr.match.PSEUDO, "" );
8933 	}
8934 
8935 	selector = Expr.relative[selector] ? selector + "*" : selector;
8936 
8937 	for ( var i = 0, l = root.length; i < l; i++ ) {
8938 		Sizzle( selector, root[i], tmpSet, seed );
8939 	}
8940 
8941 	return Sizzle.filter( later, tmpSet );
8942 };
8943 
8944 // EXPOSE
8945 
8946 window.tinymce.dom.Sizzle = Sizzle;
8947 
8948 })();
8949 
8950 
8951 (function(tinymce) {
8952 	tinymce.dom.Element = function(id, settings) {
8953 		var t = this, dom, el;
8954 
8955 		t.settings = settings = settings || {};
8956 		t.id = id;
8957 		t.dom = dom = settings.dom || tinymce.DOM;
8958 
8959 		// Only IE leaks DOM references, this is a lot faster
8960 		if (!tinymce.isIE)
8961 			el = dom.get(t.id);
8962 
8963 		tinymce.each(
8964 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
8965 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
8966 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
8967 				'isHidden,setHTML,get').split(/,/), function(k) {
8968 					t[k] = function() {
8969 						var a = [id], i;
8970 
8971 						for (i = 0; i < arguments.length; i++)
8972 							a.push(arguments[i]);
8973 
8974 						a = dom[k].apply(dom, a);
8975 						t.update(k);
8976 
8977 						return a;
8978 					};
8979 			}
8980 		);
8981 
8982 		tinymce.extend(t, {
8983 			on : function(n, f, s) {
8984 				return tinymce.dom.Event.add(t.id, n, f, s);
8985 			},
8986 
8987 			getXY : function() {
8988 				return {
8989 					x : parseInt(t.getStyle('left')),
8990 					y : parseInt(t.getStyle('top'))
8991 				};
8992 			},
8993 
8994 			getSize : function() {
8995 				var n = dom.get(t.id);
8996 
8997 				return {
8998 					w : parseInt(t.getStyle('width') || n.clientWidth),
8999 					h : parseInt(t.getStyle('height') || n.clientHeight)
9000 				};
9001 			},
9002 
9003 			moveTo : function(x, y) {
9004 				t.setStyles({left : x, top : y});
9005 			},
9006 
9007 			moveBy : function(x, y) {
9008 				var p = t.getXY();
9009 
9010 				t.moveTo(p.x + x, p.y + y);
9011 			},
9012 
9013 			resizeTo : function(w, h) {
9014 				t.setStyles({width : w, height : h});
9015 			},
9016 
9017 			resizeBy : function(w, h) {
9018 				var s = t.getSize();
9019 
9020 				t.resizeTo(s.w + w, s.h + h);
9021 			},
9022 
9023 			update : function(k) {
9024 				var b;
9025 
9026 				if (tinymce.isIE6 && settings.blocker) {
9027 					k = k || '';
9028 
9029 					// Ignore getters
9030 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9031 						return;
9032 
9033 					// Remove blocker on remove
9034 					if (k == 'remove') {
9035 						dom.remove(t.blocker);
9036 						return;
9037 					}
9038 
9039 					if (!t.blocker) {
9040 						t.blocker = dom.uniqueId();
9041 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9042 						dom.setStyle(b, 'opacity', 0);
9043 					} else
9044 						b = dom.get(t.blocker);
9045 
9046 					dom.setStyles(b, {
9047 						left : t.getStyle('left', 1),
9048 						top : t.getStyle('top', 1),
9049 						width : t.getStyle('width', 1),
9050 						height : t.getStyle('height', 1),
9051 						display : t.getStyle('display', 1),
9052 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9053 					});
9054 				}
9055 			}
9056 		});
9057 	};
9058 })(tinymce);
9059 
9060 (function(tinymce) {
9061 	function trimNl(s) {
9062 		return s.replace(/[\n\r]+/g, '');
9063 	};
9064 
9065 	// Shorten names
9066 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9067 
9068 	tinymce.create('tinymce.dom.Selection', {
9069 		Selection : function(dom, win, serializer, editor) {
9070 			var t = this;
9071 
9072 			t.dom = dom;
9073 			t.win = win;
9074 			t.serializer = serializer;
9075 			t.editor = editor;
9076 
9077 			// Add events
9078 			each([
9079 				'onBeforeSetContent',
9080 
9081 				'onBeforeGetContent',
9082 
9083 				'onSetContent',
9084 
9085 				'onGetContent'
9086 			], function(e) {
9087 				t[e] = new tinymce.util.Dispatcher(t);
9088 			});
9089 
9090 			// No W3C Range support
9091 			if (!t.win.getSelection)
9092 				t.tridentSel = new tinymce.dom.TridentSelection(t);
9093 
9094 			if (tinymce.isIE && dom.boxModel)
9095 				this._fixIESelection();
9096 
9097 			// Prevent leaks
9098 			tinymce.addUnload(t.destroy, t);
9099 		},
9100 
9101 		setCursorLocation: function(node, offset) {
9102 			var t = this; var r = t.dom.createRng();
9103 			r.setStart(node, offset);
9104 			r.setEnd(node, offset);
9105 			t.setRng(r);
9106 			t.collapse(false);
9107 		},
9108 		getContent : function(s) {
9109 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9110 
9111 			s = s || {};
9112 			wb = wa = '';
9113 			s.get = true;
9114 			s.format = s.format || 'html';
9115 			s.forced_root_block = '';
9116 			t.onBeforeGetContent.dispatch(t, s);
9117 
9118 			if (s.format == 'text')
9119 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9120 
9121 			if (r.cloneContents) {
9122 				n = r.cloneContents();
9123 
9124 				if (n)
9125 					e.appendChild(n);
9126 			} else if (is(r.item) || is(r.htmlText)) {
9127 				// IE will produce invalid markup if elements are present that
9128 				// it doesn't understand like custom elements or HTML5 elements.
9129 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
9130 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9131 				e.removeChild(e.firstChild);
9132 			} else
9133 				e.innerHTML = r.toString();
9134 
9135 			// Keep whitespace before and after
9136 			if (/^\s/.test(e.innerHTML))
9137 				wb = ' ';
9138 
9139 			if (/\s+$/.test(e.innerHTML))
9140 				wa = ' ';
9141 
9142 			s.getInner = true;
9143 
9144 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9145 			t.onGetContent.dispatch(t, s);
9146 
9147 			return s.content;
9148 		},
9149 
9150 		setContent : function(content, args) {
9151 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9152 
9153 			args = args || {format : 'html'};
9154 			args.set = true;
9155 			content = args.content = content;
9156 
9157 			// Dispatch before set content event
9158 			if (!args.no_events)
9159 				self.onBeforeSetContent.dispatch(self, args);
9160 
9161 			content = args.content;
9162 
9163 			if (rng.insertNode) {
9164 				// Make caret marker since insertNode places the caret in the beginning of text after insert
9165 				content += '<span id="__caret">_</span>';
9166 
9167 				// Delete and insert new node
9168 				if (rng.startContainer == doc && rng.endContainer == doc) {
9169 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9170 					doc.body.innerHTML = content;
9171 				} else {
9172 					rng.deleteContents();
9173 
9174 					if (doc.body.childNodes.length === 0) {
9175 						doc.body.innerHTML = content;
9176 					} else {
9177 						// createContextualFragment doesn't exists in IE 9 DOMRanges
9178 						if (rng.createContextualFragment) {
9179 							rng.insertNode(rng.createContextualFragment(content));
9180 						} else {
9181 							// Fake createContextualFragment call in IE 9
9182 							frag = doc.createDocumentFragment();
9183 							temp = doc.createElement('div');
9184 
9185 							frag.appendChild(temp);
9186 							temp.outerHTML = content;
9187 
9188 							rng.insertNode(frag);
9189 						}
9190 					}
9191 				}
9192 
9193 				// Move to caret marker
9194 				caretNode = self.dom.get('__caret');
9195 
9196 				// Make sure we wrap it compleatly, Opera fails with a simple select call
9197 				rng = doc.createRange();
9198 				rng.setStartBefore(caretNode);
9199 				rng.setEndBefore(caretNode);
9200 				self.setRng(rng);
9201 
9202 				// Remove the caret position
9203 				self.dom.remove('__caret');
9204 
9205 				try {
9206 					self.setRng(rng);
9207 				} catch (ex) {
9208 					// Might fail on Opera for some odd reason
9209 				}
9210 			} else {
9211 				if (rng.item) {
9212 					// Delete content and get caret text selection
9213 					doc.execCommand('Delete', false, null);
9214 					rng = self.getRng();
9215 				}
9216 
9217 				// Explorer removes spaces from the beginning of pasted contents
9218 				if (/^\s+/.test(content)) {
9219 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9220 					self.dom.remove('__mce_tmp');
9221 				} else
9222 					rng.pasteHTML(content);
9223 			}
9224 
9225 			// Dispatch set content event
9226 			if (!args.no_events)
9227 				self.onSetContent.dispatch(self, args);
9228 		},
9229 
9230 		getStart : function() {
9231 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9232 
9233 			if (rng.duplicate || rng.item) {
9234 				// Control selection, return first item
9235 				if (rng.item)
9236 					return rng.item(0);
9237 
9238 				// Get start element
9239 				checkRng = rng.duplicate();
9240 				checkRng.collapse(1);
9241 				startElement = checkRng.parentElement();
9242 				if (startElement.ownerDocument !== self.dom.doc) {
9243 					startElement = self.dom.getRoot();
9244 				}
9245 
9246 				// Check if range parent is inside the start element, then return the inner parent element
9247 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9248 				parentElement = node = rng.parentElement();
9249 				while (node = node.parentNode) {
9250 					if (node == startElement) {
9251 						startElement = parentElement;
9252 						break;
9253 					}
9254 				}
9255 
9256 				return startElement;
9257 			} else {
9258 				startElement = rng.startContainer;
9259 
9260 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
9261 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9262 
9263 				if (startElement && startElement.nodeType == 3)
9264 					return startElement.parentNode;
9265 
9266 				return startElement;
9267 			}
9268 		},
9269 
9270 		getEnd : function() {
9271 			var self = this, rng = self.getRng(), endElement, endOffset;
9272 
9273 			if (rng.duplicate || rng.item) {
9274 				if (rng.item)
9275 					return rng.item(0);
9276 
9277 				rng = rng.duplicate();
9278 				rng.collapse(0);
9279 				endElement = rng.parentElement();
9280 				if (endElement.ownerDocument !== self.dom.doc) {
9281 					endElement = self.dom.getRoot();
9282 				}
9283 
9284 				if (endElement && endElement.nodeName == 'BODY')
9285 					return endElement.lastChild || endElement;
9286 
9287 				return endElement;
9288 			} else {
9289 				endElement = rng.endContainer;
9290 				endOffset = rng.endOffset;
9291 
9292 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
9293 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9294 
9295 				if (endElement && endElement.nodeType == 3)
9296 					return endElement.parentNode;
9297 
9298 				return endElement;
9299 			}
9300 		},
9301 
9302 		getBookmark : function(type, normalized) {
9303 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9304 
9305 			function findIndex(name, element) {
9306 				var index = 0;
9307 
9308 				each(dom.select(name), function(node, i) {
9309 					if (node == element)
9310 						index = i;
9311 				});
9312 
9313 				return index;
9314 			};
9315 
9316 			function normalizeTableCellSelection(rng) {
9317 				function moveEndPoint(start) {
9318 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
9319 
9320 					container = rng[prefix + 'Container'];
9321 					offset = rng[prefix + 'Offset'];
9322 
9323 					if (container.nodeType == 1 && container.nodeName == "TR") {
9324 						childNodes = container.childNodes;
9325 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9326 						if (container) {
9327 							offset = start ? 0 : container.childNodes.length;
9328 							rng['set' + (start ? 'Start' : 'End')](container, offset);
9329 						}
9330 					}
9331 				};
9332 
9333 				moveEndPoint(true);
9334 				moveEndPoint();
9335 
9336 				return rng;
9337 			};
9338 
9339 			function getLocation() {
9340 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9341 
9342 				function getPoint(rng, start) {
9343 					var container = rng[start ? 'startContainer' : 'endContainer'],
9344 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9345 
9346 					if (container.nodeType == 3) {
9347 						if (normalized) {
9348 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9349 								offset += node.nodeValue.length;
9350 						}
9351 
9352 						point.push(offset);
9353 					} else {
9354 						childNodes = container.childNodes;
9355 
9356 						if (offset >= childNodes.length && childNodes.length) {
9357 							after = 1;
9358 							offset = Math.max(0, childNodes.length - 1);
9359 						}
9360 
9361 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9362 					}
9363 
9364 					for (; container && container != root; container = container.parentNode)
9365 						point.push(t.dom.nodeIndex(container, normalized));
9366 
9367 					return point;
9368 				};
9369 
9370 				bookmark.start = getPoint(rng, true);
9371 
9372 				if (!t.isCollapsed())
9373 					bookmark.end = getPoint(rng);
9374 
9375 				return bookmark;
9376 			};
9377 
9378 			if (type == 2) {
9379 				if (t.tridentSel)
9380 					return t.tridentSel.getBookmark(type);
9381 
9382 				return getLocation();
9383 			}
9384 
9385 			// Handle simple range
9386 			if (type)
9387 				return {rng : t.getRng()};
9388 
9389 			rng = t.getRng();
9390 			id = dom.uniqueId();
9391 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9392 			styles = 'overflow:hidden;line-height:0px';
9393 
9394 			// Explorer method
9395 			if (rng.duplicate || rng.item) {
9396 				// Text selection
9397 				if (!rng.item) {
9398 					rng2 = rng.duplicate();
9399 
9400 					try {
9401 						// Insert start marker
9402 						rng.collapse();
9403 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9404 
9405 						// Insert end marker
9406 						if (!collapsed) {
9407 							rng2.collapse(false);
9408 
9409 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9410 							rng.moveToElementText(rng2.parentElement());
9411 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9412 								rng2.move('character', -1);
9413 
9414 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9415 						}
9416 					} catch (ex) {
9417 						// IE might throw unspecified error so lets ignore it
9418 						return null;
9419 					}
9420 				} else {
9421 					// Control selection
9422 					element = rng.item(0);
9423 					name = element.nodeName;
9424 
9425 					return {name : name, index : findIndex(name, element)};
9426 				}
9427 			} else {
9428 				element = t.getNode();
9429 				name = element.nodeName;
9430 				if (name == 'IMG')
9431 					return {name : name, index : findIndex(name, element)};
9432 
9433 				// W3C method
9434 				rng2 = normalizeTableCellSelection(rng.cloneRange());
9435 
9436 				// Insert end marker
9437 				if (!collapsed) {
9438 					rng2.collapse(false);
9439 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9440 				}
9441 
9442 				rng = normalizeTableCellSelection(rng);
9443 				rng.collapse(true);
9444 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9445 			}
9446 
9447 			t.moveToBookmark({id : id, keep : 1});
9448 
9449 			return {id : id};
9450 		},
9451 
9452 		moveToBookmark : function(bookmark) {
9453 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
9454 
9455 			function setEndPoint(start) {
9456 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9457 
9458 				if (point) {
9459 					offset = point[0];
9460 
9461 					// Find container node
9462 					for (node = root, i = point.length - 1; i >= 1; i--) {
9463 						children = node.childNodes;
9464 
9465 						if (point[i] > children.length - 1)
9466 							return;
9467 
9468 						node = children[point[i]];
9469 					}
9470 
9471 					// Move text offset to best suitable location
9472 					if (node.nodeType === 3)
9473 						offset = Math.min(point[0], node.nodeValue.length);
9474 
9475 					// Move element offset to best suitable location
9476 					if (node.nodeType === 1)
9477 						offset = Math.min(point[0], node.childNodes.length);
9478 
9479 					// Set offset within container node
9480 					if (start)
9481 						rng.setStart(node, offset);
9482 					else
9483 						rng.setEnd(node, offset);
9484 				}
9485 
9486 				return true;
9487 			};
9488 
9489 			function restoreEndPoint(suffix) {
9490 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9491 
9492 				if (marker) {
9493 					node = marker.parentNode;
9494 
9495 					if (suffix == 'start') {
9496 						if (!keep) {
9497 							idx = dom.nodeIndex(marker);
9498 						} else {
9499 							node = marker.firstChild;
9500 							idx = 1;
9501 						}
9502 
9503 						startContainer = endContainer = node;
9504 						startOffset = endOffset = idx;
9505 					} else {
9506 						if (!keep) {
9507 							idx = dom.nodeIndex(marker);
9508 						} else {
9509 							node = marker.firstChild;
9510 							idx = 1;
9511 						}
9512 
9513 						endContainer = node;
9514 						endOffset = idx;
9515 					}
9516 
9517 					if (!keep) {
9518 						prev = marker.previousSibling;
9519 						next = marker.nextSibling;
9520 
9521 						// Remove all marker text nodes
9522 						each(tinymce.grep(marker.childNodes), function(node) {
9523 							if (node.nodeType == 3)
9524 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9525 						});
9526 
9527 						// Remove marker but keep children if for example contents where inserted into the marker
9528 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9529 						while (marker = dom.get(bookmark.id + '_' + suffix))
9530 							dom.remove(marker, 1);
9531 
9532 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9533 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9534 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9535 							idx = prev.nodeValue.length;
9536 							prev.appendData(next.nodeValue);
9537 							dom.remove(next);
9538 
9539 							if (suffix == 'start') {
9540 								startContainer = endContainer = prev;
9541 								startOffset = endOffset = idx;
9542 							} else {
9543 								endContainer = prev;
9544 								endOffset = idx;
9545 							}
9546 						}
9547 					}
9548 				}
9549 			};
9550 
9551 			function addBogus(node) {
9552 				// Adds a bogus BR element for empty block elements
9553 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
9554 					node.innerHTML = '<br data-mce-bogus="1" />';
9555 
9556 				return node;
9557 			};
9558 
9559 			if (bookmark) {
9560 				if (bookmark.start) {
9561 					rng = dom.createRng();
9562 					root = dom.getRoot();
9563 
9564 					if (t.tridentSel)
9565 						return t.tridentSel.moveToBookmark(bookmark);
9566 
9567 					if (setEndPoint(true) && setEndPoint()) {
9568 						t.setRng(rng);
9569 					}
9570 				} else if (bookmark.id) {
9571 					// Restore start/end points
9572 					restoreEndPoint('start');
9573 					restoreEndPoint('end');
9574 
9575 					if (startContainer) {
9576 						rng = dom.createRng();
9577 						rng.setStart(addBogus(startContainer), startOffset);
9578 						rng.setEnd(addBogus(endContainer), endOffset);
9579 						t.setRng(rng);
9580 					}
9581 				} else if (bookmark.name) {
9582 					t.select(dom.select(bookmark.name)[bookmark.index]);
9583 				} else if (bookmark.rng)
9584 					t.setRng(bookmark.rng);
9585 			}
9586 		},
9587 
9588 		select : function(node, content) {
9589 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
9590 
9591 			function setPoint(node, start) {
9592 				var walker = new TreeWalker(node, node);
9593 
9594 				do {
9595 					// Text node
9596 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9597 						if (start)
9598 							rng.setStart(node, 0);
9599 						else
9600 							rng.setEnd(node, node.nodeValue.length);
9601 
9602 						return;
9603 					}
9604 
9605 					// BR element
9606 					if (node.nodeName == 'BR') {
9607 						if (start)
9608 							rng.setStartBefore(node);
9609 						else
9610 							rng.setEndBefore(node);
9611 
9612 						return;
9613 					}
9614 				} while (node = (start ? walker.next() : walker.prev()));
9615 			};
9616 
9617 			if (node) {
9618 				idx = dom.nodeIndex(node);
9619 				rng.setStart(node.parentNode, idx);
9620 				rng.setEnd(node.parentNode, idx + 1);
9621 
9622 				// Find first/last text node or BR element
9623 				if (content) {
9624 					setPoint(node, 1);
9625 					setPoint(node);
9626 				}
9627 
9628 				t.setRng(rng);
9629 			}
9630 
9631 			return node;
9632 		},
9633 
9634 		isCollapsed : function() {
9635 			var t = this, r = t.getRng(), s = t.getSel();
9636 
9637 			if (!r || r.item)
9638 				return false;
9639 
9640 			if (r.compareEndPoints)
9641 				return r.compareEndPoints('StartToEnd', r) === 0;
9642 
9643 			return !s || r.collapsed;
9644 		},
9645 
9646 		collapse : function(to_start) {
9647 			var self = this, rng = self.getRng(), node;
9648 
9649 			// Control range on IE
9650 			if (rng.item) {
9651 				node = rng.item(0);
9652 				rng = self.win.document.body.createTextRange();
9653 				rng.moveToElementText(node);
9654 			}
9655 
9656 			rng.collapse(!!to_start);
9657 			self.setRng(rng);
9658 		},
9659 
9660 		getSel : function() {
9661 			var t = this, w = this.win;
9662 
9663 			return w.getSelection ? w.getSelection() : w.document.selection;
9664 		},
9665 
9666 		getRng : function(w3c) {
9667 			var self = this, selection, rng, elm, doc = self.win.document;
9668 
9669 			// Found tridentSel object then we need to use that one
9670 			if (w3c && self.tridentSel) {
9671 				return self.tridentSel.getRangeAt(0);
9672 			}
9673 
9674 			try {
9675 				if (selection = self.getSel()) {
9676 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9677 				}
9678 			} catch (ex) {
9679 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9680 			}
9681 
9682 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9683 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
9684 				elm = doc.selection.createRange().item(0);
9685 				rng = doc.createRange();
9686 				rng.setStartBefore(elm);
9687 				rng.setEndAfter(elm);
9688 			}
9689 
9690 			// No range found then create an empty one
9691 			// This can occur when the editor is placed in a hidden container element on Gecko
9692 			// Or on IE when there was an exception
9693 			if (!rng) {
9694 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9695 			}
9696 
9697 			// If range is at start of document then move it to start of body
9698 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9699 				elm = self.dom.getRoot();
9700 				rng.setStart(elm, 0);
9701 				rng.setEnd(elm, 0);
9702 			}
9703 
9704 			if (self.selectedRange && self.explicitRange) {
9705 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9706 					// Safari, Opera and Chrome only ever select text which causes the range to change.
9707 					// This lets us use the originally set range if the selection hasn't been changed by the user.
9708 					rng = self.explicitRange;
9709 				} else {
9710 					self.selectedRange = null;
9711 					self.explicitRange = null;
9712 				}
9713 			}
9714 
9715 			return rng;
9716 		},
9717 
9718 		setRng : function(r, forward) {
9719 			var s, t = this;
9720 
9721 			if (!t.tridentSel) {
9722 				s = t.getSel();
9723 
9724 				if (s) {
9725 					t.explicitRange = r;
9726 
9727 					try {
9728 						s.removeAllRanges();
9729 					} catch (ex) {
9730 						// IE9 might throw errors here don't know why
9731 					}
9732 
9733 					s.addRange(r);
9734 
9735 					// Forward is set to false and we have an extend function
9736 					if (forward === false && s.extend) {
9737 						s.collapse(r.endContainer, r.endOffset);
9738 						s.extend(r.startContainer, r.startOffset);
9739 					}
9740 
9741 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
9742 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
9743 				}
9744 			} else {
9745 				// Is W3C Range
9746 				if (r.cloneRange) {
9747 					try {
9748 						t.tridentSel.addRange(r);
9749 						return;
9750 					} catch (ex) {
9751 						//IE9 throws an error here if called before selection is placed in the editor
9752 					}
9753 				}
9754 
9755 				// Is IE specific range
9756 				try {
9757 					r.select();
9758 				} catch (ex) {
9759 					// Needed for some odd IE bug #1843306
9760 				}
9761 			}
9762 		},
9763 
9764 		setNode : function(n) {
9765 			var t = this;
9766 
9767 			t.setContent(t.dom.getOuterHTML(n));
9768 
9769 			return n;
9770 		},
9771 
9772 		getNode : function() {
9773 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
9774 
9775 			function skipEmptyTextNodes(n, forwards) {
9776 				var orig = n;
9777 				while (n && n.nodeType === 3 && n.length === 0) {
9778 					n = forwards ? n.nextSibling : n.previousSibling;
9779 				}
9780 				return n || orig;
9781 			};
9782 
9783 			// Range maybe lost after the editor is made visible again
9784 			if (!rng)
9785 				return t.dom.getRoot();
9786 
9787 			if (rng.setStart) {
9788 				elm = rng.commonAncestorContainer;
9789 
9790 				// Handle selection a image or other control like element such as anchors
9791 				if (!rng.collapsed) {
9792 					if (rng.startContainer == rng.endContainer) {
9793 						if (rng.endOffset - rng.startOffset < 2) {
9794 							if (rng.startContainer.hasChildNodes())
9795 								elm = rng.startContainer.childNodes[rng.startOffset];
9796 						}
9797 					}
9798 
9799 					// If the anchor node is a element instead of a text node then return this element
9800 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
9801 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
9802 
9803 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
9804 					// This happens when you double click an underlined word in FireFox.
9805 					if (start.nodeType === 3 && end.nodeType === 3) {
9806 						if (start.length === rng.startOffset) {
9807 							start = skipEmptyTextNodes(start.nextSibling, true);
9808 						} else {
9809 							start = start.parentNode;
9810 						}
9811 						if (rng.endOffset === 0) {
9812 							end = skipEmptyTextNodes(end.previousSibling, false);
9813 						} else {
9814 							end = end.parentNode;
9815 						}
9816 
9817 						if (start && start === end)
9818 							return start;
9819 					}
9820 				}
9821 
9822 				if (elm && elm.nodeType == 3)
9823 					return elm.parentNode;
9824 
9825 				return elm;
9826 			}
9827 
9828 			return rng.item ? rng.item(0) : rng.parentElement();
9829 		},
9830 
9831 		getSelectedBlocks : function(st, en) {
9832 			var t = this, dom = t.dom, sb, eb, n, bl = [];
9833 
9834 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
9835 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
9836 
9837 			if (sb)
9838 				bl.push(sb);
9839 
9840 			if (sb && eb && sb != eb) {
9841 				n = sb;
9842 
9843 				var walker = new TreeWalker(sb, dom.getRoot());
9844 				while ((n = walker.next()) && n != eb) {
9845 					if (dom.isBlock(n))
9846 						bl.push(n);
9847 				}
9848 			}
9849 
9850 			if (eb && sb != eb)
9851 				bl.push(eb);
9852 
9853 			return bl;
9854 		},
9855 
9856 		isForward: function(){
9857 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
9858 
9859 			// No support for selection direction then always return true
9860 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
9861 				return true;
9862 			}
9863 
9864 			anchorRange = dom.createRng();
9865 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
9866 			anchorRange.collapse(true);
9867 
9868 			focusRange = dom.createRng();
9869 			focusRange.setStart(sel.focusNode, sel.focusOffset);
9870 			focusRange.collapse(true);
9871 
9872 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
9873 		},
9874 
9875 		normalize : function() {
9876 			var self = this, rng, normalized, collapsed, node, sibling;
9877 
9878 			function normalizeEndPoint(start) {
9879 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
9880 
9881 				function hasBrBeforeAfter(node, left) {
9882 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
9883 
9884 					while (node = walker[left ? 'prev' : 'next']()) {
9885 						if (node.nodeName === "BR") {
9886 							return true;
9887 						}
9888 					}
9889 				};
9890 
9891 				// Walks the dom left/right to find a suitable text node to move the endpoint into
9892 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
9893 				function findTextNodeRelative(left, startNode) {
9894 					var walker, lastInlineElement;
9895 
9896 					startNode = startNode || container;
9897 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
9898 
9899 					// Walk left until we hit a text node we can move to or a block/br/img
9900 					while (node = walker[left ? 'prev' : 'next']()) {
9901 						// Found text node that has a length
9902 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
9903 							container = node;
9904 							offset = left ? node.nodeValue.length : 0;
9905 							normalized = true;
9906 							return;
9907 						}
9908 
9909 						// Break if we find a block or a BR/IMG/INPUT etc
9910 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9911 							return;
9912 						}
9913 
9914 						lastInlineElement = node;
9915 					}
9916 
9917 					// Only fetch the last inline element when in caret mode for now
9918 					if (collapsed && lastInlineElement) {
9919 						container = lastInlineElement;
9920 						normalized = true;
9921 						offset = 0;
9922 					}
9923 				};
9924 
9925 				container = rng[(start ? 'start' : 'end') + 'Container'];
9926 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
9927 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
9928 
9929 				// If the container is a document move it to the body element
9930 				if (container.nodeType === 9) {
9931 					container = dom.getRoot();
9932 					offset = 0;
9933 				}
9934 
9935 				// If the container is body try move it into the closest text node or position
9936 				if (container === body) {
9937 					// If start is before/after a image, table etc
9938 					if (start) {
9939 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
9940 						if (node) {
9941 							nodeName = node.nodeName.toLowerCase();
9942 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
9943 								return;
9944 							}
9945 						}
9946 					}
9947 
9948 					// Resolve the index
9949 					if (container.hasChildNodes()) {
9950 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
9951 						offset = 0;
9952 
9953 						// Don't walk into elements that doesn't have any child nodes like a IMG
9954 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
9955 							// Walk the DOM to find a text node to place the caret at or a BR
9956 							node = container;
9957 							walker = new TreeWalker(container, body);
9958 
9959 							do {
9960 								// Found a text node use that position
9961 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
9962 									offset = start ? 0 : node.nodeValue.length;
9963 									container = node;
9964 									normalized = true;
9965 									break;
9966 								}
9967 
9968 								// Found a BR/IMG element that we can place the caret before
9969 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
9970 									offset = dom.nodeIndex(node);
9971 									container = node.parentNode;
9972 
9973 									// Put caret after image when moving the end point
9974 									if (node.nodeName ==  "IMG" && !start) {
9975 										offset++;
9976 									}
9977 
9978 									normalized = true;
9979 									break;
9980 								}
9981 							} while (node = (start ? walker.next() : walker.prev()));
9982 						}
9983 					}
9984 				}
9985 
9986 				// Lean the caret to the left if possible
9987 				if (collapsed) {
9988 					// So this: <b>x</b><i>|x</i>
9989 					// Becomes: <b>x|</b><i>x</i>
9990 					// Seems that only gecko has issues with this
9991 					if (container.nodeType === 3 && offset === 0) {
9992 						findTextNodeRelative(true);
9993 					}
9994 
9995 					// Lean left into empty inline elements when the caret is before a BR
9996 					// So this: <i><b></b><i>|<br></i>
9997 					// Becomes: <i><b>|</b><i><br></i>
9998 					// Seems that only gecko has issues with this
9999 					if (container.nodeType === 1) {
10000 						node = container.childNodes[offset];
10001 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10002 							findTextNodeRelative(true, container.childNodes[offset]);
10003 						}
10004 					}
10005 				}
10006 
10007 				// Lean the start of the selection right if possible
10008 				// So this: x[<b>x]</b>
10009 				// Becomes: x<b>[x]</b>
10010 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10011 					findTextNodeRelative(false);
10012 				}
10013 
10014 				// Set endpoint if it was normalized
10015 				if (normalized)
10016 					rng['set' + (start ? 'Start' : 'End')](container, offset);
10017 			};
10018 
10019 			// Normalize only on non IE browsers for now
10020 			if (tinymce.isIE)
10021 				return;
10022 			
10023 			rng = self.getRng();
10024 			collapsed = rng.collapsed;
10025 
10026 			// Normalize the end points
10027 			normalizeEndPoint(true);
10028 
10029 			if (!collapsed)
10030 				normalizeEndPoint();
10031 
10032 			// Set the selection if it was normalized
10033 			if (normalized) {
10034 				// If it was collapsed then make sure it still is
10035 				if (collapsed) {
10036 					rng.collapse(true);
10037 				}
10038 
10039 				//console.log(self.dom.dumpRng(rng));
10040 				self.setRng(rng, self.isForward());
10041 			}
10042 		},
10043 
10044 		selectorChanged: function(selector, callback) {
10045 			var self = this, currentSelectors;
10046 
10047 			if (!self.selectorChangedData) {
10048 				self.selectorChangedData = {};
10049 				currentSelectors = {};
10050 
10051 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10052 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10053 
10054 					// Check for new matching selectors
10055 					each(self.selectorChangedData, function(callbacks, selector) {
10056 						each(parents, function(node) {
10057 							if (dom.is(node, selector)) {
10058 								if (!currentSelectors[selector]) {
10059 									// Execute callbacks
10060 									each(callbacks, function(callback) {
10061 										callback(true, {node: node, selector: selector, parents: parents});
10062 									});
10063 
10064 									currentSelectors[selector] = callbacks;
10065 								}
10066 
10067 								matchedSelectors[selector] = callbacks;
10068 								return false;
10069 							}
10070 						});
10071 					});
10072 
10073 					// Check if current selectors still match
10074 					each(currentSelectors, function(callbacks, selector) {
10075 						if (!matchedSelectors[selector]) {
10076 							delete currentSelectors[selector];
10077 
10078 							each(callbacks, function(callback) {
10079 								callback(false, {node: node, selector: selector, parents: parents});
10080 							});
10081 						}
10082 					});
10083 				});
10084 			}
10085 
10086 			// Add selector listeners
10087 			if (!self.selectorChangedData[selector]) {
10088 				self.selectorChangedData[selector] = [];
10089 			}
10090 
10091 			self.selectorChangedData[selector].push(callback);
10092 
10093 			return self;
10094 		},
10095 
10096 		destroy : function(manual) {
10097 			var self = this;
10098 
10099 			self.win = null;
10100 
10101 			// Manual destroy then remove unload handler
10102 			if (!manual)
10103 				tinymce.removeUnload(self.destroy);
10104 		},
10105 
10106 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10107 		_fixIESelection : function() {
10108 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10109 
10110 			// Return range from point or null if it failed
10111 			function rngFromPoint(x, y) {
10112 				var rng = body.createTextRange();
10113 
10114 				try {
10115 					rng.moveToPoint(x, y);
10116 				} catch (ex) {
10117 					// IE sometimes throws and exception, so lets just ignore it
10118 					rng = null;
10119 				}
10120 
10121 				return rng;
10122 			};
10123 
10124 			// Fires while the selection is changing
10125 			function selectionChange(e) {
10126 				var pointRng;
10127 
10128 				// Check if the button is down or not
10129 				if (e.button) {
10130 					// Create range from mouse position
10131 					pointRng = rngFromPoint(e.x, e.y);
10132 
10133 					if (pointRng) {
10134 						// Check if pointRange is before/after selection then change the endPoint
10135 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10136 							pointRng.setEndPoint('StartToStart', startRng);
10137 						else
10138 							pointRng.setEndPoint('EndToEnd', startRng);
10139 
10140 						pointRng.select();
10141 					}
10142 				} else
10143 					endSelection();
10144 			}
10145 
10146 			// Removes listeners
10147 			function endSelection() {
10148 				var rng = doc.selection.createRange();
10149 
10150 				// If the range is collapsed then use the last start range
10151 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10152 					startRng.select();
10153 
10154 				dom.unbind(doc, 'mouseup', endSelection);
10155 				dom.unbind(doc, 'mousemove', selectionChange);
10156 				startRng = started = 0;
10157 			};
10158 
10159 			// Make HTML element unselectable since we are going to handle selection by hand
10160 			doc.documentElement.unselectable = true;
10161 			
10162 			// Detect when user selects outside BODY
10163 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10164 				if (e.target.nodeName === 'HTML') {
10165 					if (started)
10166 						endSelection();
10167 
10168 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10169 					htmlElm = doc.documentElement;
10170 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
10171 						return;
10172 
10173 					started = 1;
10174 					// Setup start position
10175 					startRng = rngFromPoint(e.x, e.y);
10176 					if (startRng) {
10177 						// Listen for selection change events
10178 						dom.bind(doc, 'mouseup', endSelection);
10179 						dom.bind(doc, 'mousemove', selectionChange);
10180 
10181 						dom.win.focus();
10182 						startRng.select();
10183 					}
10184 				}
10185 			});
10186 		}
10187 	});
10188 })(tinymce);
10189 
10190 (function(tinymce) {
10191 	tinymce.dom.Serializer = function(settings, dom, schema) {
10192 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10193 
10194 		// Support the old apply_source_formatting option
10195 		if (!settings.apply_source_formatting)
10196 			settings.indent = false;
10197 
10198 		// Default DOM and Schema if they are undefined
10199 		dom = dom || tinymce.DOM;
10200 		schema = schema || new tinymce.html.Schema(settings);
10201 		settings.entity_encoding = settings.entity_encoding || 'named';
10202 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10203 
10204 		onPreProcess = new tinymce.util.Dispatcher(self);
10205 
10206 		onPostProcess = new tinymce.util.Dispatcher(self);
10207 
10208 		htmlParser = new tinymce.html.DomParser(settings, schema);
10209 
10210 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10211 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10212 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10213 
10214 			while (i--) {
10215 				node = nodes[i];
10216 
10217 				value = node.attributes.map[internalName];
10218 				if (value !== undef) {
10219 					// Set external name to internal value and remove internal
10220 					node.attr(name, value.length > 0 ? value : null);
10221 					node.attr(internalName, null);
10222 				} else {
10223 					// No internal attribute found then convert the value we have in the DOM
10224 					value = node.attributes.map[name];
10225 
10226 					if (name === "style")
10227 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
10228 					else if (urlConverter)
10229 						value = urlConverter.call(urlConverterScope, value, name, node.name);
10230 
10231 					node.attr(name, value.length > 0 ? value : null);
10232 				}
10233 			}
10234 		});
10235 
10236 		// Remove internal classes mceItem<..> or mceSelected
10237 		htmlParser.addAttributeFilter('class', function(nodes, name) {
10238 			var i = nodes.length, node, value;
10239 
10240 			while (i--) {
10241 				node = nodes[i];
10242 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10243 				node.attr('class', value.length > 0 ? value : null);
10244 			}
10245 		});
10246 
10247 		// Remove bookmark elements
10248 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10249 			var i = nodes.length, node;
10250 
10251 			while (i--) {
10252 				node = nodes[i];
10253 
10254 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10255 					node.remove();
10256 			}
10257 		});
10258 
10259 		// Remove expando attributes
10260 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10261 			var i = nodes.length;
10262 
10263 			while (i--) {
10264 				nodes[i].attr(name, null);
10265 			}
10266 		});
10267 
10268 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
10269 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
10270 			var i = nodes.length, node, value;
10271 
10272 			function trim(value) {
10273 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10274 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
10275 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10276 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10277 			};
10278 
10279 			while (i--) {
10280 				node = nodes[i];
10281 				value = node.firstChild ? node.firstChild.value : '';
10282 
10283 				if (name === "script") {
10284 					// Remove mce- prefix from script elements
10285 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10286 
10287 					if (value.length > 0)
10288 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10289 				} else {
10290 					if (value.length > 0)
10291 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10292 				}
10293 			}
10294 		});
10295 
10296 		// Convert comments to cdata and handle protected comments
10297 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
10298 			var i = nodes.length, node;
10299 
10300 			while (i--) {
10301 				node = nodes[i];
10302 
10303 				if (node.value.indexOf('[CDATA[') === 0) {
10304 					node.name = '#cdata';
10305 					node.type = 4;
10306 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10307 				} else if (node.value.indexOf('mce:protected ') === 0) {
10308 					node.name = "#text";
10309 					node.type = 3;
10310 					node.raw = true;
10311 					node.value = unescape(node.value).substr(14);
10312 				}
10313 			}
10314 		});
10315 
10316 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10317 			var i = nodes.length, node;
10318 
10319 			while (i--) {
10320 				node = nodes[i];
10321 				if (node.type === 7)
10322 					node.remove();
10323 				else if (node.type === 1) {
10324 					if (name === "input" && !("type" in node.attributes.map))
10325 						node.attr('type', 'text');
10326 				}
10327 			}
10328 		});
10329 
10330 		// Fix list elements, TODO: Replace this later
10331 		if (settings.fix_list_elements) {
10332 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10333 				var i = nodes.length, node, parentNode;
10334 
10335 				while (i--) {
10336 					node = nodes[i];
10337 					parentNode = node.parent;
10338 
10339 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10340 						if (node.prev && node.prev.name === 'li') {
10341 							node.prev.append(node);
10342 						}
10343 					}
10344 				}
10345 			});
10346 		}
10347 
10348 		// Remove internal data attributes
10349 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10350 			var i = nodes.length;
10351 
10352 			while (i--) {
10353 				nodes[i].attr(name, null);
10354 			}
10355 		});
10356 
10357 		// Return public methods
10358 		return {
10359 			schema : schema,
10360 
10361 			addNodeFilter : htmlParser.addNodeFilter,
10362 
10363 			addAttributeFilter : htmlParser.addAttributeFilter,
10364 
10365 			onPreProcess : onPreProcess,
10366 
10367 			onPostProcess : onPostProcess,
10368 
10369 			serialize : function(node, args) {
10370 				var impl, doc, oldDoc, htmlSerializer, content;
10371 
10372 				// Explorer won't clone contents of script and style and the
10373 				// selected index of select elements are cleared on a clone operation.
10374 				if (isIE && dom.select('script,style,select,map').length > 0) {
10375 					content = node.innerHTML;
10376 					node = node.cloneNode(false);
10377 					dom.setHTML(node, content);
10378 				} else
10379 					node = node.cloneNode(true);
10380 
10381 				// Nodes needs to be attached to something in WebKit/Opera
10382 				// Older builds of Opera crashes if you attach the node to an document created dynamically
10383 				// and since we can't feature detect a crash we need to sniff the acutal build number
10384 				// This fix will make DOM ranges and make Sizzle happy!
10385 				impl = node.ownerDocument.implementation;
10386 				if (impl.createHTMLDocument) {
10387 					// Create an empty HTML document
10388 					doc = impl.createHTMLDocument("");
10389 
10390 					// Add the element or it's children if it's a body element to the new document
10391 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10392 						doc.body.appendChild(doc.importNode(node, true));
10393 					});
10394 
10395 					// Grab first child or body element for serialization
10396 					if (node.nodeName != 'BODY')
10397 						node = doc.body.firstChild;
10398 					else
10399 						node = doc.body;
10400 
10401 					// set the new document in DOMUtils so createElement etc works
10402 					oldDoc = dom.doc;
10403 					dom.doc = doc;
10404 				}
10405 
10406 				args = args || {};
10407 				args.format = args.format || 'html';
10408 
10409 				// Pre process
10410 				if (!args.no_events) {
10411 					args.node = node;
10412 					onPreProcess.dispatch(self, args);
10413 				}
10414 
10415 				// Setup serializer
10416 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
10417 
10418 				// Parse and serialize HTML
10419 				args.content = htmlSerializer.serialize(
10420 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10421 				);
10422 
10423 				// Replace all BOM characters for now until we can find a better solution
10424 				if (!args.cleanup)
10425 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
10426 
10427 				// Post process
10428 				if (!args.no_events)
10429 					onPostProcess.dispatch(self, args);
10430 
10431 				// Restore the old document if it was changed
10432 				if (oldDoc)
10433 					dom.doc = oldDoc;
10434 
10435 				args.node = null;
10436 
10437 				return args.content;
10438 			},
10439 
10440 			addRules : function(rules) {
10441 				schema.addValidElements(rules);
10442 			},
10443 
10444 			setRules : function(rules) {
10445 				schema.setValidElements(rules);
10446 			}
10447 		};
10448 	};
10449 })(tinymce);
10450 (function(tinymce) {
10451 	tinymce.dom.ScriptLoader = function(settings) {
10452 		var QUEUED = 0,
10453 			LOADING = 1,
10454 			LOADED = 2,
10455 			states = {},
10456 			queue = [],
10457 			scriptLoadedCallbacks = {},
10458 			queueLoadedCallbacks = [],
10459 			loading = 0,
10460 			undef;
10461 
10462 		function loadScript(url, callback) {
10463 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10464 
10465 			// Execute callback when script is loaded
10466 			function done() {
10467 				dom.remove(id);
10468 
10469 				if (elm)
10470 					elm.onreadystatechange = elm.onload = elm = null;
10471 
10472 				callback();
10473 			};
10474 			
10475 			function error() {
10476 				// Report the error so it's easier for people to spot loading errors
10477 				if (typeof(console) !== "undefined" && console.log)
10478 					console.log("Failed to load: " + url);
10479 
10480 				// We can't mark it as done if there is a load error since
10481 				// A) We don't want to produce 404 errors on the server and
10482 				// B) the onerror event won't fire on all browsers.
10483 				// done();
10484 			};
10485 
10486 			id = dom.uniqueId();
10487 
10488 			if (tinymce.isIE6) {
10489 				uri = new tinymce.util.URI(url);
10490 				loc = location;
10491 
10492 				// If script is from same domain and we
10493 				// use IE 6 then use XHR since it's more reliable
10494 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10495 					tinymce.util.XHR.send({
10496 						url : tinymce._addVer(uri.getURI()),
10497 						success : function(content) {
10498 							// Create new temp script element
10499 							var script = dom.create('script', {
10500 								type : 'text/javascript'
10501 							});
10502 
10503 							// Evaluate script in global scope
10504 							script.text = content;
10505 							document.getElementsByTagName('head')[0].appendChild(script);
10506 							dom.remove(script);
10507 
10508 							done();
10509 						},
10510 						
10511 						error : error
10512 					});
10513 
10514 					return;
10515 				}
10516 			}
10517 
10518 			// Create new script element
10519 			elm = document.createElement('script');
10520 			elm.id = id;
10521 			elm.type = 'text/javascript';
10522 			elm.src = tinymce._addVer(url);
10523 
10524 			// Add onload listener for non IE browsers since IE9
10525 			// fires onload event before the script is parsed and executed
10526 			if (!tinymce.isIE)
10527 				elm.onload = done;
10528 
10529 			// Add onerror event will get fired on some browsers but not all of them
10530 			elm.onerror = error;
10531 
10532 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10533 			if (!tinymce.isOpera) {
10534 				elm.onreadystatechange = function() {
10535 					var state = elm.readyState;
10536 
10537 					// Loaded state is passed on IE 6 however there
10538 					// are known issues with this method but we can't use
10539 					// XHR in a cross domain loading
10540 					if (state == 'complete' || state == 'loaded')
10541 						done();
10542 				};
10543 			}
10544 
10545 			// Most browsers support this feature so we report errors
10546 			// for those at least to help users track their missing plugins etc
10547 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10548 			/*elm.onerror = function() {
10549 				alert('Failed to load: ' + url);
10550 			};*/
10551 
10552 			// Add script to document
10553 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10554 		};
10555 
10556 		this.isDone = function(url) {
10557 			return states[url] == LOADED;
10558 		};
10559 
10560 		this.markDone = function(url) {
10561 			states[url] = LOADED;
10562 		};
10563 
10564 		this.add = this.load = function(url, callback, scope) {
10565 			var item, state = states[url];
10566 
10567 			// Add url to load queue
10568 			if (state == undef) {
10569 				queue.push(url);
10570 				states[url] = QUEUED;
10571 			}
10572 
10573 			if (callback) {
10574 				// Store away callback for later execution
10575 				if (!scriptLoadedCallbacks[url])
10576 					scriptLoadedCallbacks[url] = [];
10577 
10578 				scriptLoadedCallbacks[url].push({
10579 					func : callback,
10580 					scope : scope || this
10581 				});
10582 			}
10583 		};
10584 
10585 		this.loadQueue = function(callback, scope) {
10586 			this.loadScripts(queue, callback, scope);
10587 		};
10588 
10589 		this.loadScripts = function(scripts, callback, scope) {
10590 			var loadScripts;
10591 
10592 			function execScriptLoadedCallbacks(url) {
10593 				// Execute URL callback functions
10594 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10595 					callback.func.call(callback.scope);
10596 				});
10597 
10598 				scriptLoadedCallbacks[url] = undef;
10599 			};
10600 
10601 			queueLoadedCallbacks.push({
10602 				func : callback,
10603 				scope : scope || this
10604 			});
10605 
10606 			loadScripts = function() {
10607 				var loadingScripts = tinymce.grep(scripts);
10608 
10609 				// Current scripts has been handled
10610 				scripts.length = 0;
10611 
10612 				// Load scripts that needs to be loaded
10613 				tinymce.each(loadingScripts, function(url) {
10614 					// Script is already loaded then execute script callbacks directly
10615 					if (states[url] == LOADED) {
10616 						execScriptLoadedCallbacks(url);
10617 						return;
10618 					}
10619 
10620 					// Is script not loading then start loading it
10621 					if (states[url] != LOADING) {
10622 						states[url] = LOADING;
10623 						loading++;
10624 
10625 						loadScript(url, function() {
10626 							states[url] = LOADED;
10627 							loading--;
10628 
10629 							execScriptLoadedCallbacks(url);
10630 
10631 							// Load more scripts if they where added by the recently loaded script
10632 							loadScripts();
10633 						});
10634 					}
10635 				});
10636 
10637 				// No scripts are currently loading then execute all pending queue loaded callbacks
10638 				if (!loading) {
10639 					tinymce.each(queueLoadedCallbacks, function(callback) {
10640 						callback.func.call(callback.scope);
10641 					});
10642 
10643 					queueLoadedCallbacks.length = 0;
10644 				}
10645 			};
10646 
10647 			loadScripts();
10648 		};
10649 	};
10650 
10651 	// Global script loader
10652 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10653 })(tinymce);
10654 
10655 (function(tinymce) {
10656 	tinymce.dom.RangeUtils = function(dom) {
10657 		var INVISIBLE_CHAR = '\uFEFF';
10658 
10659 		this.walk = function(rng, callback) {
10660 			var startContainer = rng.startContainer,
10661 				startOffset = rng.startOffset,
10662 				endContainer = rng.endContainer,
10663 				endOffset = rng.endOffset,
10664 				ancestor, startPoint,
10665 				endPoint, node, parent, siblings, nodes;
10666 
10667 			// Handle table cell selection the table plugin enables
10668 			// you to fake select table cells and perform formatting actions on them
10669 			nodes = dom.select('td.mceSelected,th.mceSelected');
10670 			if (nodes.length > 0) {
10671 				tinymce.each(nodes, function(node) {
10672 					callback([node]);
10673 				});
10674 
10675 				return;
10676 			}
10677 
10678 			function exclude(nodes) {
10679 				var node;
10680 
10681 				// First node is excluded
10682 				node = nodes[0];
10683 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10684 					nodes.splice(0, 1);
10685 				}
10686 
10687 				// Last node is excluded
10688 				node = nodes[nodes.length - 1];
10689 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
10690 					nodes.splice(nodes.length - 1, 1);
10691 				}
10692 
10693 				return nodes;
10694 			};
10695 
10696 			function collectSiblings(node, name, end_node) {
10697 				var siblings = [];
10698 
10699 				for (; node && node != end_node; node = node[name])
10700 					siblings.push(node);
10701 
10702 				return siblings;
10703 			};
10704 
10705 			function findEndPoint(node, root) {
10706 				do {
10707 					if (node.parentNode == root)
10708 						return node;
10709 
10710 					node = node.parentNode;
10711 				} while(node);
10712 			};
10713 
10714 			function walkBoundary(start_node, end_node, next) {
10715 				var siblingName = next ? 'nextSibling' : 'previousSibling';
10716 
10717 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
10718 					parent = node.parentNode;
10719 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
10720 
10721 					if (siblings.length) {
10722 						if (!next)
10723 							siblings.reverse();
10724 
10725 						callback(exclude(siblings));
10726 					}
10727 				}
10728 			};
10729 
10730 			// If index based start position then resolve it
10731 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
10732 				startContainer = startContainer.childNodes[startOffset];
10733 
10734 			// If index based end position then resolve it
10735 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
10736 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
10737 
10738 			// Same container
10739 			if (startContainer == endContainer)
10740 				return callback(exclude([startContainer]));
10741 
10742 			// Find common ancestor and end points
10743 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
10744 				
10745 			// Process left side
10746 			for (node = startContainer; node; node = node.parentNode) {
10747 				if (node === endContainer)
10748 					return walkBoundary(startContainer, ancestor, true);
10749 
10750 				if (node === ancestor)
10751 					break;
10752 			}
10753 
10754 			// Process right side
10755 			for (node = endContainer; node; node = node.parentNode) {
10756 				if (node === startContainer)
10757 					return walkBoundary(endContainer, ancestor);
10758 
10759 				if (node === ancestor)
10760 					break;
10761 			}
10762 
10763 			// Find start/end point
10764 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
10765 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
10766 
10767 			// Walk left leaf
10768 			walkBoundary(startContainer, startPoint, true);
10769 
10770 			// Walk the middle from start to end point
10771 			siblings = collectSiblings(
10772 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
10773 				'nextSibling',
10774 				endPoint == endContainer ? endPoint.nextSibling : endPoint
10775 			);
10776 
10777 			if (siblings.length)
10778 				callback(exclude(siblings));
10779 
10780 			// Walk right leaf
10781 			walkBoundary(endContainer, endPoint);
10782 		};
10783 
10784 		this.split = function(rng) {
10785 			var startContainer = rng.startContainer,
10786 				startOffset = rng.startOffset,
10787 				endContainer = rng.endContainer,
10788 				endOffset = rng.endOffset;
10789 
10790 			function splitText(node, offset) {
10791 				return node.splitText(offset);
10792 			};
10793 
10794 			// Handle single text node
10795 			if (startContainer == endContainer && startContainer.nodeType == 3) {
10796 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10797 					endContainer = splitText(startContainer, startOffset);
10798 					startContainer = endContainer.previousSibling;
10799 
10800 					if (endOffset > startOffset) {
10801 						endOffset = endOffset - startOffset;
10802 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
10803 						endOffset = endContainer.nodeValue.length;
10804 						startOffset = 0;
10805 					} else {
10806 						endOffset = 0;
10807 					}
10808 				}
10809 			} else {
10810 				// Split startContainer text node if needed
10811 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
10812 					startContainer = splitText(startContainer, startOffset);
10813 					startOffset = 0;
10814 				}
10815 
10816 				// Split endContainer text node if needed
10817 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
10818 					endContainer = splitText(endContainer, endOffset).previousSibling;
10819 					endOffset = endContainer.nodeValue.length;
10820 				}
10821 			}
10822 
10823 			return {
10824 				startContainer : startContainer,
10825 				startOffset : startOffset,
10826 				endContainer : endContainer,
10827 				endOffset : endOffset
10828 			};
10829 		};
10830 
10831 	};
10832 
10833 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
10834 		if (rng1 && rng2) {
10835 			// Compare native IE ranges
10836 			if (rng1.item || rng1.duplicate) {
10837 				// Both are control ranges and the selected element matches
10838 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
10839 					return true;
10840 
10841 				// Both are text ranges and the range matches
10842 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
10843 					return true;
10844 			} else {
10845 				// Compare w3c ranges
10846 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
10847 			}
10848 		}
10849 
10850 		return false;
10851 	};
10852 })(tinymce);
10853 
10854 (function(tinymce) {
10855 	var Event = tinymce.dom.Event, each = tinymce.each;
10856 
10857 	tinymce.create('tinymce.ui.KeyboardNavigation', {
10858 		KeyboardNavigation: function(settings, dom) {
10859 			var t = this, root = settings.root, items = settings.items,
10860 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
10861 					excludeFromTabOrder = settings.excludeFromTabOrder,
10862 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
10863 
10864 			dom = dom || tinymce.DOM;
10865 
10866 			itemFocussed = function(evt) {
10867 				focussedId = evt.target.id;
10868 			};
10869 			
10870 			itemBlurred = function(evt) {
10871 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
10872 			};
10873 			
10874 			rootFocussed = function(evt) {
10875 				var item = dom.get(focussedId);
10876 				dom.setAttrib(item, 'tabindex', '0');
10877 				item.focus();
10878 			};
10879 			
10880 			t.focus = function() {
10881 				dom.get(focussedId).focus();
10882 			};
10883 
10884 			t.destroy = function() {
10885 				each(items, function(item) {
10886 					var elm = dom.get(item.id);
10887 
10888 					dom.unbind(elm, 'focus', itemFocussed);
10889 					dom.unbind(elm, 'blur', itemBlurred);
10890 				});
10891 
10892 				var rootElm = dom.get(root);
10893 				dom.unbind(rootElm, 'focus', rootFocussed);
10894 				dom.unbind(rootElm, 'keydown', rootKeydown);
10895 
10896 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
10897 				t.destroy = function() {};
10898 			};
10899 			
10900 			t.moveFocus = function(dir, evt) {
10901 				var idx = -1, controls = t.controls, newFocus;
10902 
10903 				if (!focussedId)
10904 					return;
10905 
10906 				each(items, function(item, index) {
10907 					if (item.id === focussedId) {
10908 						idx = index;
10909 						return false;
10910 					}
10911 				});
10912 
10913 				idx += dir;
10914 				if (idx < 0) {
10915 					idx = items.length - 1;
10916 				} else if (idx >= items.length) {
10917 					idx = 0;
10918 				}
10919 				
10920 				newFocus = items[idx];
10921 				dom.setAttrib(focussedId, 'tabindex', '-1');
10922 				dom.setAttrib(newFocus.id, 'tabindex', '0');
10923 				dom.get(newFocus.id).focus();
10924 
10925 				if (settings.actOnFocus) {
10926 					settings.onAction(newFocus.id);
10927 				}
10928 
10929 				if (evt)
10930 					Event.cancel(evt);
10931 			};
10932 			
10933 			rootKeydown = function(evt) {
10934 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
10935 				
10936 				switch (evt.keyCode) {
10937 					case DOM_VK_LEFT:
10938 						if (enableLeftRight) t.moveFocus(-1);
10939 						break;
10940 	
10941 					case DOM_VK_RIGHT:
10942 						if (enableLeftRight) t.moveFocus(1);
10943 						break;
10944 	
10945 					case DOM_VK_UP:
10946 						if (enableUpDown) t.moveFocus(-1);
10947 						break;
10948 
10949 					case DOM_VK_DOWN:
10950 						if (enableUpDown) t.moveFocus(1);
10951 						break;
10952 
10953 					case DOM_VK_ESCAPE:
10954 						if (settings.onCancel) {
10955 							settings.onCancel();
10956 							Event.cancel(evt);
10957 						}
10958 						break;
10959 
10960 					case DOM_VK_ENTER:
10961 					case DOM_VK_RETURN:
10962 					case DOM_VK_SPACE:
10963 						if (settings.onAction) {
10964 							settings.onAction(focussedId);
10965 							Event.cancel(evt);
10966 						}
10967 						break;
10968 				}
10969 			};
10970 
10971 			// Set up state and listeners for each item.
10972 			each(items, function(item, idx) {
10973 				var tabindex, elm;
10974 
10975 				if (!item.id) {
10976 					item.id = dom.uniqueId('_mce_item_');
10977 				}
10978 
10979 				elm = dom.get(item.id);
10980 
10981 				if (excludeFromTabOrder) {
10982 					dom.bind(elm, 'blur', itemBlurred);
10983 					tabindex = '-1';
10984 				} else {
10985 					tabindex = (idx === 0 ? '0' : '-1');
10986 				}
10987 
10988 				elm.setAttribute('tabindex', tabindex);
10989 				dom.bind(elm, 'focus', itemFocussed);
10990 			});
10991 			
10992 			// Setup initial state for root element.
10993 			if (items[0]){
10994 				focussedId = items[0].id;
10995 			}
10996 
10997 			dom.setAttrib(root, 'tabindex', '-1');
10998 
10999 			// Setup listeners for root element.
11000 			var rootElm = dom.get(root);
11001 			dom.bind(rootElm, 'focus', rootFocussed);
11002 			dom.bind(rootElm, 'keydown', rootKeydown);
11003 		}
11004 	});
11005 })(tinymce);
11006 
11007 (function(tinymce) {
11008 	// Shorten class names
11009 	var DOM = tinymce.DOM, is = tinymce.is;
11010 
11011 	tinymce.create('tinymce.ui.Control', {
11012 		Control : function(id, s, editor) {
11013 			this.id = id;
11014 			this.settings = s = s || {};
11015 			this.rendered = false;
11016 			this.onRender = new tinymce.util.Dispatcher(this);
11017 			this.classPrefix = '';
11018 			this.scope = s.scope || this;
11019 			this.disabled = 0;
11020 			this.active = 0;
11021 			this.editor = editor;
11022 		},
11023 		
11024 		setAriaProperty : function(property, value) {
11025 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11026 			if (element) {
11027 				DOM.setAttrib(element, 'aria-' + property, !!value);
11028 			}
11029 		},
11030 		
11031 		focus : function() {
11032 			DOM.get(this.id).focus();
11033 		},
11034 
11035 		setDisabled : function(s) {
11036 			if (s != this.disabled) {
11037 				this.setAriaProperty('disabled', s);
11038 
11039 				this.setState('Disabled', s);
11040 				this.setState('Enabled', !s);
11041 				this.disabled = s;
11042 			}
11043 		},
11044 
11045 		isDisabled : function() {
11046 			return this.disabled;
11047 		},
11048 
11049 		setActive : function(s) {
11050 			if (s != this.active) {
11051 				this.setState('Active', s);
11052 				this.active = s;
11053 				this.setAriaProperty('pressed', s);
11054 			}
11055 		},
11056 
11057 		isActive : function() {
11058 			return this.active;
11059 		},
11060 
11061 		setState : function(c, s) {
11062 			var n = DOM.get(this.id);
11063 
11064 			c = this.classPrefix + c;
11065 
11066 			if (s)
11067 				DOM.addClass(n, c);
11068 			else
11069 				DOM.removeClass(n, c);
11070 		},
11071 
11072 		isRendered : function() {
11073 			return this.rendered;
11074 		},
11075 
11076 		renderHTML : function() {
11077 		},
11078 
11079 		renderTo : function(n) {
11080 			DOM.setHTML(n, this.renderHTML());
11081 		},
11082 
11083 		postRender : function() {
11084 			var t = this, b;
11085 
11086 			// Set pending states
11087 			if (is(t.disabled)) {
11088 				b = t.disabled;
11089 				t.disabled = -1;
11090 				t.setDisabled(b);
11091 			}
11092 
11093 			if (is(t.active)) {
11094 				b = t.active;
11095 				t.active = -1;
11096 				t.setActive(b);
11097 			}
11098 		},
11099 
11100 		remove : function() {
11101 			DOM.remove(this.id);
11102 			this.destroy();
11103 		},
11104 
11105 		destroy : function() {
11106 			tinymce.dom.Event.clear(this.id);
11107 		}
11108 	});
11109 })(tinymce);
11110 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11111 	Container : function(id, s, editor) {
11112 		this.parent(id, s, editor);
11113 
11114 		this.controls = [];
11115 
11116 		this.lookup = {};
11117 	},
11118 
11119 	add : function(c) {
11120 		this.lookup[c.id] = c;
11121 		this.controls.push(c);
11122 
11123 		return c;
11124 	},
11125 
11126 	get : function(n) {
11127 		return this.lookup[n];
11128 	}
11129 });
11130 
11131 
11132 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11133 	Separator : function(id, s) {
11134 		this.parent(id, s);
11135 		this.classPrefix = 'mceSeparator';
11136 		this.setDisabled(true);
11137 	},
11138 
11139 	renderHTML : function() {
11140 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11141 	}
11142 });
11143 
11144 (function(tinymce) {
11145 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11146 
11147 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11148 		MenuItem : function(id, s) {
11149 			this.parent(id, s);
11150 			this.classPrefix = 'mceMenuItem';
11151 		},
11152 
11153 		setSelected : function(s) {
11154 			this.setState('Selected', s);
11155 			this.setAriaProperty('checked', !!s);
11156 			this.selected = s;
11157 		},
11158 
11159 		isSelected : function() {
11160 			return this.selected;
11161 		},
11162 
11163 		postRender : function() {
11164 			var t = this;
11165 			
11166 			t.parent();
11167 
11168 			// Set pending state
11169 			if (is(t.selected))
11170 				t.setSelected(t.selected);
11171 		}
11172 	});
11173 })(tinymce);
11174 
11175 (function(tinymce) {
11176 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11177 
11178 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11179 		Menu : function(id, s) {
11180 			var t = this;
11181 
11182 			t.parent(id, s);
11183 			t.items = {};
11184 			t.collapsed = false;
11185 			t.menuCount = 0;
11186 			t.onAddItem = new tinymce.util.Dispatcher(this);
11187 		},
11188 
11189 		expand : function(d) {
11190 			var t = this;
11191 
11192 			if (d) {
11193 				walk(t, function(o) {
11194 					if (o.expand)
11195 						o.expand();
11196 				}, 'items', t);
11197 			}
11198 
11199 			t.collapsed = false;
11200 		},
11201 
11202 		collapse : function(d) {
11203 			var t = this;
11204 
11205 			if (d) {
11206 				walk(t, function(o) {
11207 					if (o.collapse)
11208 						o.collapse();
11209 				}, 'items', t);
11210 			}
11211 
11212 			t.collapsed = true;
11213 		},
11214 
11215 		isCollapsed : function() {
11216 			return this.collapsed;
11217 		},
11218 
11219 		add : function(o) {
11220 			if (!o.settings)
11221 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11222 
11223 			this.onAddItem.dispatch(this, o);
11224 
11225 			return this.items[o.id] = o;
11226 		},
11227 
11228 		addSeparator : function() {
11229 			return this.add({separator : true});
11230 		},
11231 
11232 		addMenu : function(o) {
11233 			if (!o.collapse)
11234 				o = this.createMenu(o);
11235 
11236 			this.menuCount++;
11237 
11238 			return this.add(o);
11239 		},
11240 
11241 		hasMenus : function() {
11242 			return this.menuCount !== 0;
11243 		},
11244 
11245 		remove : function(o) {
11246 			delete this.items[o.id];
11247 		},
11248 
11249 		removeAll : function() {
11250 			var t = this;
11251 
11252 			walk(t, function(o) {
11253 				if (o.removeAll)
11254 					o.removeAll();
11255 				else
11256 					o.remove();
11257 
11258 				o.destroy();
11259 			}, 'items', t);
11260 
11261 			t.items = {};
11262 		},
11263 
11264 		createMenu : function(o) {
11265 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11266 
11267 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11268 
11269 			return m;
11270 		}
11271 	});
11272 })(tinymce);
11273 (function(tinymce) {
11274 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11275 
11276 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11277 		DropMenu : function(id, s) {
11278 			s = s || {};
11279 			s.container = s.container || DOM.doc.body;
11280 			s.offset_x = s.offset_x || 0;
11281 			s.offset_y = s.offset_y || 0;
11282 			s.vp_offset_x = s.vp_offset_x || 0;
11283 			s.vp_offset_y = s.vp_offset_y || 0;
11284 
11285 			if (is(s.icons) && !s.icons)
11286 				s['class'] += ' mceNoIcons';
11287 
11288 			this.parent(id, s);
11289 			this.onShowMenu = new tinymce.util.Dispatcher(this);
11290 			this.onHideMenu = new tinymce.util.Dispatcher(this);
11291 			this.classPrefix = 'mceMenu';
11292 		},
11293 
11294 		createMenu : function(s) {
11295 			var t = this, cs = t.settings, m;
11296 
11297 			s.container = s.container || cs.container;
11298 			s.parent = t;
11299 			s.constrain = s.constrain || cs.constrain;
11300 			s['class'] = s['class'] || cs['class'];
11301 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11302 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11303 			s.keyboard_focus = cs.keyboard_focus;
11304 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11305 
11306 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11307 
11308 			return m;
11309 		},
11310 		
11311 		focus : function() {
11312 			var t = this;
11313 			if (t.keyboardNav) {
11314 				t.keyboardNav.focus();
11315 			}
11316 		},
11317 
11318 		update : function() {
11319 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11320 
11321 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11322 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11323 
11324 			if (!DOM.boxModel)
11325 				t.element.setStyles({width : tw + 2, height : th + 2});
11326 			else
11327 				t.element.setStyles({width : tw, height : th});
11328 
11329 			if (s.max_width)
11330 				DOM.setStyle(co, 'width', tw);
11331 
11332 			if (s.max_height) {
11333 				DOM.setStyle(co, 'height', th);
11334 
11335 				if (tb.clientHeight < s.max_height)
11336 					DOM.setStyle(co, 'overflow', 'hidden');
11337 			}
11338 		},
11339 
11340 		showMenu : function(x, y, px) {
11341 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11342 
11343 			t.collapse(1);
11344 
11345 			if (t.isMenuVisible)
11346 				return;
11347 
11348 			if (!t.rendered) {
11349 				co = DOM.add(t.settings.container, t.renderNode());
11350 
11351 				each(t.items, function(o) {
11352 					o.postRender();
11353 				});
11354 
11355 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11356 			} else
11357 				co = DOM.get('menu_' + t.id);
11358 
11359 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11360 			if (!tinymce.isOpera)
11361 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11362 
11363 			DOM.show(co);
11364 			t.update();
11365 
11366 			x += s.offset_x || 0;
11367 			y += s.offset_y || 0;
11368 			vp.w -= 4;
11369 			vp.h -= 4;
11370 
11371 			// Move inside viewport if not submenu
11372 			if (s.constrain) {
11373 				w = co.clientWidth - ot;
11374 				h = co.clientHeight - ot;
11375 				mx = vp.x + vp.w;
11376 				my = vp.y + vp.h;
11377 
11378 				if ((x + s.vp_offset_x + w) > mx)
11379 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11380 
11381 				if ((y + s.vp_offset_y + h) > my)
11382 					y = Math.max(0, (my - s.vp_offset_y) - h);
11383 			}
11384 
11385 			DOM.setStyles(co, {left : x , top : y});
11386 			t.element.update();
11387 
11388 			t.isMenuVisible = 1;
11389 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
11390 				var m;
11391 
11392 				e = e.target;
11393 
11394 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11395 					m = t.items[e.id];
11396 
11397 					if (m.isDisabled())
11398 						return;
11399 
11400 					dm = t;
11401 
11402 					while (dm) {
11403 						if (dm.hideMenu)
11404 							dm.hideMenu();
11405 
11406 						dm = dm.settings.parent;
11407 					}
11408 
11409 					if (m.settings.onclick)
11410 						m.settings.onclick(e);
11411 
11412 					return false; // Cancel to fix onbeforeunload problem
11413 				}
11414 			});
11415 
11416 			if (t.hasMenus()) {
11417 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11418 					var m, r, mi;
11419 
11420 					e = e.target;
11421 					if (e && (e = DOM.getParent(e, 'tr'))) {
11422 						m = t.items[e.id];
11423 
11424 						if (t.lastMenu)
11425 							t.lastMenu.collapse(1);
11426 
11427 						if (m.isDisabled())
11428 							return;
11429 
11430 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11431 							//p = DOM.getPos(s.container);
11432 							r = DOM.getRect(e);
11433 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11434 							t.lastMenu = m;
11435 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11436 						}
11437 					}
11438 				});
11439 			}
11440 			
11441 			Event.add(co, 'keydown', t._keyHandler, t);
11442 
11443 			t.onShowMenu.dispatch(t);
11444 
11445 			if (s.keyboard_focus) { 
11446 				t._setupKeyboardNav(); 
11447 			}
11448 		},
11449 
11450 		hideMenu : function(c) {
11451 			var t = this, co = DOM.get('menu_' + t.id), e;
11452 
11453 			if (!t.isMenuVisible)
11454 				return;
11455 
11456 			if (t.keyboardNav) t.keyboardNav.destroy();
11457 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11458 			Event.remove(co, 'click', t.mouseClickFunc);
11459 			Event.remove(co, 'keydown', t._keyHandler);
11460 			DOM.hide(co);
11461 			t.isMenuVisible = 0;
11462 
11463 			if (!c)
11464 				t.collapse(1);
11465 
11466 			if (t.element)
11467 				t.element.hide();
11468 
11469 			if (e = DOM.get(t.id))
11470 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11471 
11472 			t.onHideMenu.dispatch(t);
11473 		},
11474 
11475 		add : function(o) {
11476 			var t = this, co;
11477 
11478 			o = t.parent(o);
11479 
11480 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11481 				t._add(DOM.select('tbody', co)[0], o);
11482 
11483 			return o;
11484 		},
11485 
11486 		collapse : function(d) {
11487 			this.parent(d);
11488 			this.hideMenu(1);
11489 		},
11490 
11491 		remove : function(o) {
11492 			DOM.remove(o.id);
11493 			this.destroy();
11494 
11495 			return this.parent(o);
11496 		},
11497 
11498 		destroy : function() {
11499 			var t = this, co = DOM.get('menu_' + t.id);
11500 
11501 			if (t.keyboardNav) t.keyboardNav.destroy();
11502 			Event.remove(co, 'mouseover', t.mouseOverFunc);
11503 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11504 			Event.remove(co, 'click', t.mouseClickFunc);
11505 			Event.remove(co, 'keydown', t._keyHandler);
11506 
11507 			if (t.element)
11508 				t.element.remove();
11509 
11510 			DOM.remove(co);
11511 		},
11512 
11513 		renderNode : function() {
11514 			var t = this, s = t.settings, n, tb, co, w;
11515 
11516 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11517 			if (t.settings.parent) {
11518 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11519 			}
11520 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11521 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11522 
11523 			if (s.menu_line)
11524 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11525 
11526 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11527 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11528 			tb = DOM.add(n, 'tbody');
11529 
11530 			each(t.items, function(o) {
11531 				t._add(tb, o);
11532 			});
11533 
11534 			t.rendered = true;
11535 
11536 			return w;
11537 		},
11538 
11539 		// Internal functions
11540 		_setupKeyboardNav : function(){
11541 			var contextMenu, menuItems, t=this; 
11542 			contextMenu = DOM.get('menu_' + t.id);
11543 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11544 			menuItems.splice(0,0,contextMenu);
11545 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11546 				root: 'menu_' + t.id,
11547 				items: menuItems,
11548 				onCancel: function() {
11549 					t.hideMenu();
11550 				},
11551 				enableUpDown: true
11552 			});
11553 			contextMenu.focus();
11554 		},
11555 
11556 		_keyHandler : function(evt) {
11557 			var t = this, e;
11558 			switch (evt.keyCode) {
11559 				case 37: // Left
11560 					if (t.settings.parent) {
11561 						t.hideMenu();
11562 						t.settings.parent.focus();
11563 						Event.cancel(evt);
11564 					}
11565 					break;
11566 				case 39: // Right
11567 					if (t.mouseOverFunc)
11568 						t.mouseOverFunc(evt);
11569 					break;
11570 			}
11571 		},
11572 
11573 		_add : function(tb, o) {
11574 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11575 
11576 			if (s.separator) {
11577 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11578 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11579 
11580 				if (n = ro.previousSibling)
11581 					DOM.addClass(n, 'mceLast');
11582 
11583 				return;
11584 			}
11585 
11586 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11587 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11588 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11589 
11590 			if (s.parent) {
11591 				DOM.setAttrib(a, 'aria-haspopup', 'true');
11592 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11593 			}
11594 
11595 			DOM.addClass(it, s['class']);
11596 //			n = DOM.add(n, 'span', {'class' : 'item'});
11597 
11598 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11599 
11600 			if (s.icon_src)
11601 				DOM.add(ic, 'img', {src : s.icon_src});
11602 
11603 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11604 
11605 			if (o.settings.style) {
11606 				if (typeof o.settings.style == "function")
11607 					o.settings.style = o.settings.style();
11608 
11609 				DOM.setAttrib(n, 'style', o.settings.style);
11610 			}
11611 
11612 			if (tb.childNodes.length == 1)
11613 				DOM.addClass(ro, 'mceFirst');
11614 
11615 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11616 				DOM.addClass(ro, 'mceFirst');
11617 
11618 			if (o.collapse)
11619 				DOM.addClass(ro, cp + 'ItemSub');
11620 
11621 			if (n = ro.previousSibling)
11622 				DOM.removeClass(n, 'mceLast');
11623 
11624 			DOM.addClass(ro, 'mceLast');
11625 		}
11626 	});
11627 })(tinymce);
11628 (function(tinymce) {
11629 	var DOM = tinymce.DOM;
11630 
11631 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11632 		Button : function(id, s, ed) {
11633 			this.parent(id, s, ed);
11634 			this.classPrefix = 'mceButton';
11635 		},
11636 
11637 		renderHTML : function() {
11638 			var cp = this.classPrefix, s = this.settings, h, l;
11639 
11640 			l = DOM.encode(s.label || '');
11641 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11642 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11643 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11644 			else
11645 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11646 
11647 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
11648 			h += '</a>';
11649 			return h;
11650 		},
11651 
11652 		postRender : function() {
11653 			var t = this, s = t.settings, imgBookmark;
11654 
11655 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11656 			// need to keep the selection in case the selection is lost
11657 			if (tinymce.isIE && t.editor) {
11658 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11659 					var nodeName = t.editor.selection.getNode().nodeName;
11660 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11661 				});
11662 			}
11663 			tinymce.dom.Event.add(t.id, 'click', function(e) {
11664 				if (!t.isDisabled()) {
11665 					// restore the selection in case the selection is lost in IE
11666 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
11667 						t.editor.selection.moveToBookmark(imgBookmark);
11668 					}
11669 					return s.onclick.call(s.scope, e);
11670 				}
11671 			});
11672 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
11673 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
11674 					return s.onclick.call(s.scope, e);
11675 			});
11676 		}
11677 	});
11678 })(tinymce);
11679 
11680 (function(tinymce) {
11681 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11682 
11683 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11684 		ListBox : function(id, s, ed) {
11685 			var t = this;
11686 
11687 			t.parent(id, s, ed);
11688 
11689 			t.items = [];
11690 
11691 			t.onChange = new Dispatcher(t);
11692 
11693 			t.onPostRender = new Dispatcher(t);
11694 
11695 			t.onAdd = new Dispatcher(t);
11696 
11697 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
11698 
11699 			t.classPrefix = 'mceListBox';
11700 			t.marked = {};
11701 		},
11702 
11703 		select : function(va) {
11704 			var t = this, fv, f;
11705 
11706 			t.marked = {};
11707 
11708 			if (va == undef)
11709 				return t.selectByIndex(-1);
11710 
11711 			// Is string or number make function selector
11712 			if (va && typeof(va)=="function")
11713 				f = va;
11714 			else {
11715 				f = function(v) {
11716 					return v == va;
11717 				};
11718 			}
11719 
11720 			// Do we need to do something?
11721 			if (va != t.selectedValue) {
11722 				// Find item
11723 				each(t.items, function(o, i) {
11724 					if (f(o.value)) {
11725 						fv = 1;
11726 						t.selectByIndex(i);
11727 						return false;
11728 					}
11729 				});
11730 
11731 				if (!fv)
11732 					t.selectByIndex(-1);
11733 			}
11734 		},
11735 
11736 		selectByIndex : function(idx) {
11737 			var t = this, e, o, label;
11738 
11739 			t.marked = {};
11740 
11741 			if (idx != t.selectedIndex) {
11742 				e = DOM.get(t.id + '_text');
11743 				label = DOM.get(t.id + '_voiceDesc');
11744 				o = t.items[idx];
11745 
11746 				if (o) {
11747 					t.selectedValue = o.value;
11748 					t.selectedIndex = idx;
11749 					DOM.setHTML(e, DOM.encode(o.title));
11750 					DOM.setHTML(label, t.settings.title + " - " + o.title);
11751 					DOM.removeClass(e, 'mceTitle');
11752 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
11753 				} else {
11754 					DOM.setHTML(e, DOM.encode(t.settings.title));
11755 					DOM.setHTML(label, DOM.encode(t.settings.title));
11756 					DOM.addClass(e, 'mceTitle');
11757 					t.selectedValue = t.selectedIndex = null;
11758 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
11759 				}
11760 				e = 0;
11761 			}
11762 		},
11763 
11764 		mark : function(value) {
11765 			this.marked[value] = true;
11766 		},
11767 
11768 		add : function(n, v, o) {
11769 			var t = this;
11770 
11771 			o = o || {};
11772 			o = tinymce.extend(o, {
11773 				title : n,
11774 				value : v
11775 			});
11776 
11777 			t.items.push(o);
11778 			t.onAdd.dispatch(t, o);
11779 		},
11780 
11781 		getLength : function() {
11782 			return this.items.length;
11783 		},
11784 
11785 		renderHTML : function() {
11786 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
11787 
11788 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
11789 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
11790 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
11791 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
11792 			h += '</tr></tbody></table></span>';
11793 
11794 			return h;
11795 		},
11796 
11797 		showMenu : function() {
11798 			var t = this, p2, e = DOM.get(this.id), m;
11799 
11800 			if (t.isDisabled() || t.items.length === 0)
11801 				return;
11802 
11803 			if (t.menu && t.menu.isMenuVisible)
11804 				return t.hideMenu();
11805 
11806 			if (!t.isMenuRendered) {
11807 				t.renderMenu();
11808 				t.isMenuRendered = true;
11809 			}
11810 
11811 			p2 = DOM.getPos(e);
11812 
11813 			m = t.menu;
11814 			m.settings.offset_x = p2.x;
11815 			m.settings.offset_y = p2.y;
11816 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
11817 
11818 			// Select in menu
11819 			each(t.items, function(o) {
11820 				if (m.items[o.id]) {
11821 					m.items[o.id].setSelected(0);
11822 				}
11823 			});
11824 
11825 			each(t.items, function(o) {
11826 				if (m.items[o.id] && t.marked[o.value]) {
11827 					m.items[o.id].setSelected(1);
11828 				}
11829 
11830 				if (o.value === t.selectedValue) {
11831 					m.items[o.id].setSelected(1);
11832 				}
11833 			});
11834 
11835 			m.showMenu(0, e.clientHeight);
11836 
11837 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11838 			DOM.addClass(t.id, t.classPrefix + 'Selected');
11839 
11840 			//DOM.get(t.id + '_text').focus();
11841 		},
11842 
11843 		hideMenu : function(e) {
11844 			var t = this;
11845 
11846 			if (t.menu && t.menu.isMenuVisible) {
11847 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
11848 
11849 				// Prevent double toogles by canceling the mouse click event to the button
11850 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
11851 					return;
11852 
11853 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
11854 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
11855 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11856 					t.menu.hideMenu();
11857 				}
11858 			}
11859 		},
11860 
11861 		renderMenu : function() {
11862 			var t = this, m;
11863 
11864 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
11865 				menu_line : 1,
11866 				'class' : t.classPrefix + 'Menu mceNoIcons',
11867 				max_width : 250,
11868 				max_height : 150
11869 			});
11870 
11871 			m.onHideMenu.add(function() {
11872 				t.hideMenu();
11873 				t.focus();
11874 			});
11875 
11876 			m.add({
11877 				title : t.settings.title,
11878 				'class' : 'mceMenuItemTitle',
11879 				onclick : function() {
11880 					if (t.settings.onselect('') !== false)
11881 						t.select(''); // Must be runned after
11882 				}
11883 			});
11884 
11885 			each(t.items, function(o) {
11886 				// No value then treat it as a title
11887 				if (o.value === undef) {
11888 					m.add({
11889 						title : o.title,
11890 						role : "option",
11891 						'class' : 'mceMenuItemTitle',
11892 						onclick : function() {
11893 							if (t.settings.onselect('') !== false)
11894 								t.select(''); // Must be runned after
11895 						}
11896 					});
11897 				} else {
11898 					o.id = DOM.uniqueId();
11899 					o.role= "option";
11900 					o.onclick = function() {
11901 						if (t.settings.onselect(o.value) !== false)
11902 							t.select(o.value); // Must be runned after
11903 					};
11904 
11905 					m.add(o);
11906 				}
11907 			});
11908 
11909 			t.onRenderMenu.dispatch(t, m);
11910 			t.menu = m;
11911 		},
11912 
11913 		postRender : function() {
11914 			var t = this, cp = t.classPrefix;
11915 
11916 			Event.add(t.id, 'click', t.showMenu, t);
11917 			Event.add(t.id, 'keydown', function(evt) {
11918 				if (evt.keyCode == 32) { // Space
11919 					t.showMenu(evt);
11920 					Event.cancel(evt);
11921 				}
11922 			});
11923 			Event.add(t.id, 'focus', function() {
11924 				if (!t._focused) {
11925 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
11926 						if (e.keyCode == 40) {
11927 							t.showMenu();
11928 							Event.cancel(e);
11929 						}
11930 					});
11931 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
11932 						var v;
11933 						if (e.keyCode == 13) {
11934 							// Fake select on enter
11935 							v = t.selectedValue;
11936 							t.selectedValue = null; // Needs to be null to fake change
11937 							Event.cancel(e);
11938 							t.settings.onselect(v);
11939 						}
11940 					});
11941 				}
11942 
11943 				t._focused = 1;
11944 			});
11945 			Event.add(t.id, 'blur', function() {
11946 				Event.remove(t.id, 'keydown', t.keyDownHandler);
11947 				Event.remove(t.id, 'keypress', t.keyPressHandler);
11948 				t._focused = 0;
11949 			});
11950 
11951 			// Old IE doesn't have hover on all elements
11952 			if (tinymce.isIE6 || !DOM.boxModel) {
11953 				Event.add(t.id, 'mouseover', function() {
11954 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11955 						DOM.addClass(t.id, cp + 'Hover');
11956 				});
11957 
11958 				Event.add(t.id, 'mouseout', function() {
11959 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
11960 						DOM.removeClass(t.id, cp + 'Hover');
11961 				});
11962 			}
11963 
11964 			t.onPostRender.dispatch(t, DOM.get(t.id));
11965 		},
11966 
11967 		destroy : function() {
11968 			this.parent();
11969 
11970 			Event.clear(this.id + '_text');
11971 			Event.clear(this.id + '_open');
11972 		}
11973 	});
11974 })(tinymce);
11975 
11976 (function(tinymce) {
11977 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11978 
11979 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
11980 		NativeListBox : function(id, s) {
11981 			this.parent(id, s);
11982 			this.classPrefix = 'mceNativeListBox';
11983 		},
11984 
11985 		setDisabled : function(s) {
11986 			DOM.get(this.id).disabled = s;
11987 			this.setAriaProperty('disabled', s);
11988 		},
11989 
11990 		isDisabled : function() {
11991 			return DOM.get(this.id).disabled;
11992 		},
11993 
11994 		select : function(va) {
11995 			var t = this, fv, f;
11996 
11997 			if (va == undef)
11998 				return t.selectByIndex(-1);
11999 
12000 			// Is string or number make function selector
12001 			if (va && typeof(va)=="function")
12002 				f = va;
12003 			else {
12004 				f = function(v) {
12005 					return v == va;
12006 				};
12007 			}
12008 
12009 			// Do we need to do something?
12010 			if (va != t.selectedValue) {
12011 				// Find item
12012 				each(t.items, function(o, i) {
12013 					if (f(o.value)) {
12014 						fv = 1;
12015 						t.selectByIndex(i);
12016 						return false;
12017 					}
12018 				});
12019 
12020 				if (!fv)
12021 					t.selectByIndex(-1);
12022 			}
12023 		},
12024 
12025 		selectByIndex : function(idx) {
12026 			DOM.get(this.id).selectedIndex = idx + 1;
12027 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12028 		},
12029 
12030 		add : function(n, v, a) {
12031 			var o, t = this;
12032 
12033 			a = a || {};
12034 			a.value = v;
12035 
12036 			if (t.isRendered())
12037 				DOM.add(DOM.get(this.id), 'option', a, n);
12038 
12039 			o = {
12040 				title : n,
12041 				value : v,
12042 				attribs : a
12043 			};
12044 
12045 			t.items.push(o);
12046 			t.onAdd.dispatch(t, o);
12047 		},
12048 
12049 		getLength : function() {
12050 			return this.items.length;
12051 		},
12052 
12053 		renderHTML : function() {
12054 			var h, t = this;
12055 
12056 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12057 
12058 			each(t.items, function(it) {
12059 				h += DOM.createHTML('option', {value : it.value}, it.title);
12060 			});
12061 
12062 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12063 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12064 			return h;
12065 		},
12066 
12067 		postRender : function() {
12068 			var t = this, ch, changeListenerAdded = true;
12069 
12070 			t.rendered = true;
12071 
12072 			function onChange(e) {
12073 				var v = t.items[e.target.selectedIndex - 1];
12074 
12075 				if (v && (v = v.value)) {
12076 					t.onChange.dispatch(t, v);
12077 
12078 					if (t.settings.onselect)
12079 						t.settings.onselect(v);
12080 				}
12081 			};
12082 
12083 			Event.add(t.id, 'change', onChange);
12084 
12085 			// Accessibility keyhandler
12086 			Event.add(t.id, 'keydown', function(e) {
12087 				var bf;
12088 
12089 				Event.remove(t.id, 'change', ch);
12090 				changeListenerAdded = false;
12091 
12092 				bf = Event.add(t.id, 'blur', function() {
12093 					if (changeListenerAdded) return;
12094 					changeListenerAdded = true;
12095 					Event.add(t.id, 'change', onChange);
12096 					Event.remove(t.id, 'blur', bf);
12097 				});
12098 
12099 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
12100 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
12101 					return Event.prevent(e);
12102 				}
12103 				
12104 				if (e.keyCode == 13 || e.keyCode == 32) {
12105 					onChange(e);
12106 					return Event.cancel(e);
12107 				}
12108 			});
12109 
12110 			t.onPostRender.dispatch(t, DOM.get(t.id));
12111 		}
12112 	});
12113 })(tinymce);
12114 
12115 (function(tinymce) {
12116 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12117 
12118 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12119 		MenuButton : function(id, s, ed) {
12120 			this.parent(id, s, ed);
12121 
12122 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
12123 
12124 			s.menu_container = s.menu_container || DOM.doc.body;
12125 		},
12126 
12127 		showMenu : function() {
12128 			var t = this, p1, p2, e = DOM.get(t.id), m;
12129 
12130 			if (t.isDisabled())
12131 				return;
12132 
12133 			if (!t.isMenuRendered) {
12134 				t.renderMenu();
12135 				t.isMenuRendered = true;
12136 			}
12137 
12138 			if (t.isMenuVisible)
12139 				return t.hideMenu();
12140 
12141 			p1 = DOM.getPos(t.settings.menu_container);
12142 			p2 = DOM.getPos(e);
12143 
12144 			m = t.menu;
12145 			m.settings.offset_x = p2.x;
12146 			m.settings.offset_y = p2.y;
12147 			m.settings.vp_offset_x = p2.x;
12148 			m.settings.vp_offset_y = p2.y;
12149 			m.settings.keyboard_focus = t._focused;
12150 			m.showMenu(0, e.firstChild.clientHeight);
12151 
12152 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12153 			t.setState('Selected', 1);
12154 
12155 			t.isMenuVisible = 1;
12156 		},
12157 
12158 		renderMenu : function() {
12159 			var t = this, m;
12160 
12161 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12162 				menu_line : 1,
12163 				'class' : this.classPrefix + 'Menu',
12164 				icons : t.settings.icons
12165 			});
12166 
12167 			m.onHideMenu.add(function() {
12168 				t.hideMenu();
12169 				t.focus();
12170 			});
12171 
12172 			t.onRenderMenu.dispatch(t, m);
12173 			t.menu = m;
12174 		},
12175 
12176 		hideMenu : function(e) {
12177 			var t = this;
12178 
12179 			// Prevent double toogles by canceling the mouse click event to the button
12180 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12181 				return;
12182 
12183 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12184 				t.setState('Selected', 0);
12185 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12186 				if (t.menu)
12187 					t.menu.hideMenu();
12188 			}
12189 
12190 			t.isMenuVisible = 0;
12191 		},
12192 
12193 		postRender : function() {
12194 			var t = this, s = t.settings;
12195 
12196 			Event.add(t.id, 'click', function() {
12197 				if (!t.isDisabled()) {
12198 					if (s.onclick)
12199 						s.onclick(t.value);
12200 
12201 					t.showMenu();
12202 				}
12203 			});
12204 		}
12205 	});
12206 })(tinymce);
12207 
12208 (function(tinymce) {
12209 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12210 
12211 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12212 		SplitButton : function(id, s, ed) {
12213 			this.parent(id, s, ed);
12214 			this.classPrefix = 'mceSplitButton';
12215 		},
12216 
12217 		renderHTML : function() {
12218 			var h, t = this, s = t.settings, h1;
12219 
12220 			h = '<tbody><tr>';
12221 
12222 			if (s.image)
12223 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12224 			else
12225 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12226 
12227 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12228 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12229 	
12230 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12231 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12232 
12233 			h += '</tr></tbody>';
12234 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12235 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12236 		},
12237 
12238 		postRender : function() {
12239 			var t = this, s = t.settings, activate;
12240 
12241 			if (s.onclick) {
12242 				activate = function(evt) {
12243 					if (!t.isDisabled()) {
12244 						s.onclick(t.value);
12245 						Event.cancel(evt);
12246 					}
12247 				};
12248 				Event.add(t.id + '_action', 'click', activate);
12249 				Event.add(t.id, ['click', 'keydown'], function(evt) {
12250 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12251 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12252 						activate();
12253 						Event.cancel(evt);
12254 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12255 						t.showMenu();
12256 						Event.cancel(evt);
12257 					}
12258 				});
12259 			}
12260 
12261 			Event.add(t.id + '_open', 'click', function (evt) {
12262 				t.showMenu();
12263 				Event.cancel(evt);
12264 			});
12265 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12266 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12267 
12268 			// Old IE doesn't have hover on all elements
12269 			if (tinymce.isIE6 || !DOM.boxModel) {
12270 				Event.add(t.id, 'mouseover', function() {
12271 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12272 						DOM.addClass(t.id, 'mceSplitButtonHover');
12273 				});
12274 
12275 				Event.add(t.id, 'mouseout', function() {
12276 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12277 						DOM.removeClass(t.id, 'mceSplitButtonHover');
12278 				});
12279 			}
12280 		},
12281 
12282 		destroy : function() {
12283 			this.parent();
12284 
12285 			Event.clear(this.id + '_action');
12286 			Event.clear(this.id + '_open');
12287 			Event.clear(this.id);
12288 		}
12289 	});
12290 })(tinymce);
12291 
12292 (function(tinymce) {
12293 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12294 
12295 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12296 		ColorSplitButton : function(id, s, ed) {
12297 			var t = this;
12298 
12299 			t.parent(id, s, ed);
12300 
12301 			t.settings = s = tinymce.extend({
12302 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12303 				grid_width : 8,
12304 				default_color : '#888888'
12305 			}, t.settings);
12306 
12307 			t.onShowMenu = new tinymce.util.Dispatcher(t);
12308 
12309 			t.onHideMenu = new tinymce.util.Dispatcher(t);
12310 
12311 			t.value = s.default_color;
12312 		},
12313 
12314 		showMenu : function() {
12315 			var t = this, r, p, e, p2;
12316 
12317 			if (t.isDisabled())
12318 				return;
12319 
12320 			if (!t.isMenuRendered) {
12321 				t.renderMenu();
12322 				t.isMenuRendered = true;
12323 			}
12324 
12325 			if (t.isMenuVisible)
12326 				return t.hideMenu();
12327 
12328 			e = DOM.get(t.id);
12329 			DOM.show(t.id + '_menu');
12330 			DOM.addClass(e, 'mceSplitButtonSelected');
12331 			p2 = DOM.getPos(e);
12332 			DOM.setStyles(t.id + '_menu', {
12333 				left : p2.x,
12334 				top : p2.y + e.firstChild.clientHeight,
12335 				zIndex : 200000
12336 			});
12337 			e = 0;
12338 
12339 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12340 			t.onShowMenu.dispatch(t);
12341 
12342 			if (t._focused) {
12343 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12344 					if (e.keyCode == 27)
12345 						t.hideMenu();
12346 				});
12347 
12348 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12349 			}
12350 
12351 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12352 				root: t.id + '_menu',
12353 				items: DOM.select('a', t.id + '_menu'),
12354 				onCancel: function() {
12355 					t.hideMenu();
12356 					t.focus();
12357 				}
12358 			});
12359 
12360 			t.keyboardNav.focus();
12361 			t.isMenuVisible = 1;
12362 		},
12363 
12364 		hideMenu : function(e) {
12365 			var t = this;
12366 
12367 			if (t.isMenuVisible) {
12368 				// Prevent double toogles by canceling the mouse click event to the button
12369 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12370 					return;
12371 
12372 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12373 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
12374 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12375 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12376 					DOM.hide(t.id + '_menu');
12377 				}
12378 
12379 				t.isMenuVisible = 0;
12380 				t.onHideMenu.dispatch();
12381 				t.keyboardNav.destroy();
12382 			}
12383 		},
12384 
12385 		renderMenu : function() {
12386 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12387 
12388 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12389 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12390 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12391 
12392 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12393 			tb = DOM.add(n, 'tbody');
12394 
12395 			// Generate color grid
12396 			i = 0;
12397 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12398 				c = c.replace(/^#/, '');
12399 
12400 				if (!i--) {
12401 					tr = DOM.add(tb, 'tr');
12402 					i = s.grid_width - 1;
12403 				}
12404 
12405 				n = DOM.add(tr, 'td');
12406 				var settings = {
12407 					href : 'javascript:;',
12408 					style : {
12409 						backgroundColor : '#' + c
12410 					},
12411 					'title': t.editor.getLang('colors.' + c, c),
12412 					'data-mce-color' : '#' + c
12413 				};
12414 
12415 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12416 				if (!tinymce.isIE ) {
12417 					settings.role = 'option';
12418 				}
12419 
12420 				n = DOM.add(n, 'a', settings);
12421 
12422 				if (t.editor.forcedHighContrastMode) {
12423 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12424 					if (n.getContext && (context = n.getContext("2d"))) {
12425 						context.fillStyle = '#' + c;
12426 						context.fillRect(0, 0, 16, 16);
12427 					} else {
12428 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
12429 						DOM.remove(n);
12430 					}
12431 				}
12432 			});
12433 
12434 			if (s.more_colors_func) {
12435 				n = DOM.add(tb, 'tr');
12436 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12437 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12438 
12439 				Event.add(n, 'click', function(e) {
12440 					s.more_colors_func.call(s.more_colors_scope || this);
12441 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
12442 				});
12443 			}
12444 
12445 			DOM.addClass(m, 'mceColorSplitMenu');
12446 
12447 			// Prevent IE from scrolling and hindering click to occur #4019
12448 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12449 
12450 			Event.add(t.id + '_menu', 'click', function(e) {
12451 				var c;
12452 
12453 				e = DOM.getParent(e.target, 'a', tb);
12454 
12455 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12456 					t.setColor(c);
12457 
12458 				return false; // Prevent IE auto save warning
12459 			});
12460 
12461 			return w;
12462 		},
12463 
12464 		setColor : function(c) {
12465 			this.displayColor(c);
12466 			this.hideMenu();
12467 			this.settings.onselect(c);
12468 		},
12469 		
12470 		displayColor : function(c) {
12471 			var t = this;
12472 
12473 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12474 
12475 			t.value = c;
12476 		},
12477 
12478 		postRender : function() {
12479 			var t = this, id = t.id;
12480 
12481 			t.parent();
12482 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12483 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12484 		},
12485 
12486 		destroy : function() {
12487 			var self = this;
12488 
12489 			self.parent();
12490 
12491 			Event.clear(self.id + '_menu');
12492 			Event.clear(self.id + '_more');
12493 			DOM.remove(self.id + '_menu');
12494 
12495 			if (self.keyboardNav) {
12496 				self.keyboardNav.destroy();
12497 			}
12498 		}
12499 	});
12500 })(tinymce);
12501 
12502 (function(tinymce) {
12503 // Shorten class names
12504 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12505 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12506 	renderHTML : function() {
12507 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12508 
12509 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12510 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12511 		h.push("<span role='application'>");
12512 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12513 		each(controls, function(toolbar) {
12514 			h.push(toolbar.renderHTML());
12515 		});
12516 		h.push("</span>");
12517 		h.push('</div>');
12518 
12519 		return h.join('');
12520 	},
12521 	
12522 	focus : function() {
12523 		var t = this;
12524 		dom.get(t.id).focus();
12525 	},
12526 	
12527 	postRender : function() {
12528 		var t = this, items = [];
12529 
12530 		each(t.controls, function(toolbar) {
12531 			each (toolbar.controls, function(control) {
12532 				if (control.id) {
12533 					items.push(control);
12534 				}
12535 			});
12536 		});
12537 
12538 		t.keyNav = new tinymce.ui.KeyboardNavigation({
12539 			root: t.id,
12540 			items: items,
12541 			onCancel: function() {
12542 				//Move focus if webkit so that navigation back will read the item.
12543 				if (tinymce.isWebKit) {
12544 					dom.get(t.editor.id+"_ifr").focus();
12545 				}
12546 				t.editor.focus();
12547 			},
12548 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
12549 		});
12550 	},
12551 	
12552 	destroy : function() {
12553 		var self = this;
12554 
12555 		self.parent();
12556 		self.keyNav.destroy();
12557 		Event.clear(self.id);
12558 	}
12559 });
12560 })(tinymce);
12561 
12562 (function(tinymce) {
12563 // Shorten class names
12564 var dom = tinymce.DOM, each = tinymce.each;
12565 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12566 	renderHTML : function() {
12567 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12568 
12569 		cl = t.controls;
12570 		for (i=0; i<cl.length; i++) {
12571 			// Get current control, prev control, next control and if the control is a list box or not
12572 			co = cl[i];
12573 			pr = cl[i - 1];
12574 			nx = cl[i + 1];
12575 
12576 			// Add toolbar start
12577 			if (i === 0) {
12578 				c = 'mceToolbarStart';
12579 
12580 				if (co.Button)
12581 					c += ' mceToolbarStartButton';
12582 				else if (co.SplitButton)
12583 					c += ' mceToolbarStartSplitButton';
12584 				else if (co.ListBox)
12585 					c += ' mceToolbarStartListBox';
12586 
12587 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12588 			}
12589 
12590 			// Add toolbar end before list box and after the previous button
12591 			// This is to fix the o2k7 editor skins
12592 			if (pr && co.ListBox) {
12593 				if (pr.Button || pr.SplitButton)
12594 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12595 			}
12596 
12597 			// Render control HTML
12598 
12599 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
12600 			if (dom.stdMode)
12601 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12602 			else
12603 				h += '<td>' + co.renderHTML() + '</td>';
12604 
12605 			// Add toolbar start after list box and before the next button
12606 			// This is to fix the o2k7 editor skins
12607 			if (nx && co.ListBox) {
12608 				if (nx.Button || nx.SplitButton)
12609 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12610 			}
12611 		}
12612 
12613 		c = 'mceToolbarEnd';
12614 
12615 		if (co.Button)
12616 			c += ' mceToolbarEndButton';
12617 		else if (co.SplitButton)
12618 			c += ' mceToolbarEndSplitButton';
12619 		else if (co.ListBox)
12620 			c += ' mceToolbarEndListBox';
12621 
12622 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12623 
12624 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12625 	}
12626 });
12627 })(tinymce);
12628 
12629 (function(tinymce) {
12630 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12631 
12632 	tinymce.create('tinymce.AddOnManager', {
12633 		AddOnManager : function() {
12634 			var self = this;
12635 
12636 			self.items = [];
12637 			self.urls = {};
12638 			self.lookup = {};
12639 			self.onAdd = new Dispatcher(self);
12640 		},
12641 
12642 		get : function(n) {
12643 			if (this.lookup[n]) {
12644 				return this.lookup[n].instance;
12645 			} else {
12646 				return undefined;
12647 			}
12648 		},
12649 
12650 		dependencies : function(n) {
12651 			var result;
12652 			if (this.lookup[n]) {
12653 				result = this.lookup[n].dependencies;
12654 			}
12655 			return result || [];
12656 		},
12657 
12658 		requireLangPack : function(n) {
12659 			var s = tinymce.settings;
12660 
12661 			if (s && s.language && s.language_load !== false)
12662 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12663 		},
12664 
12665 		add : function(id, o, dependencies) {
12666 			this.items.push(o);
12667 			this.lookup[id] = {instance:o, dependencies:dependencies};
12668 			this.onAdd.dispatch(this, id, o);
12669 
12670 			return o;
12671 		},
12672 		createUrl: function(baseUrl, dep) {
12673 			if (typeof dep === "object") {
12674 				return dep
12675 			} else {
12676 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12677 			}
12678 		},
12679 
12680 		addComponents: function(pluginName, scripts) {
12681 			var pluginUrl = this.urls[pluginName];
12682 			tinymce.each(scripts, function(script){
12683 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
12684 			});
12685 		},
12686 
12687 		load : function(n, u, cb, s) {
12688 			var t = this, url = u;
12689 
12690 			function loadDependencies() {
12691 				var dependencies = t.dependencies(n);
12692 				tinymce.each(dependencies, function(dep) {
12693 					var newUrl = t.createUrl(u, dep);
12694 					t.load(newUrl.resource, newUrl, undefined, undefined);
12695 				});
12696 				if (cb) {
12697 					if (s) {
12698 						cb.call(s);
12699 					} else {
12700 						cb.call(tinymce.ScriptLoader);
12701 					}
12702 				}
12703 			}
12704 
12705 			if (t.urls[n])
12706 				return;
12707 			if (typeof u === "object")
12708 				url = u.prefix + u.resource + u.suffix;
12709 
12710 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
12711 				url = tinymce.baseURL + '/' + url;
12712 
12713 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
12714 
12715 			if (t.lookup[n]) {
12716 				loadDependencies();
12717 			} else {
12718 				tinymce.ScriptLoader.add(url, loadDependencies, s);
12719 			}
12720 		}
12721 	});
12722 
12723 	// Create plugin and theme managers
12724 	tinymce.PluginManager = new tinymce.AddOnManager();
12725 	tinymce.ThemeManager = new tinymce.AddOnManager();
12726 }(tinymce));
12727 
12728 (function(tinymce) {
12729 	// Shorten names
12730 	var each = tinymce.each, extend = tinymce.extend,
12731 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
12732 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
12733 		explode = tinymce.explode,
12734 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
12735 
12736 	// Setup some URLs where the editor API is located and where the document is
12737 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
12738 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
12739 		tinymce.documentBaseURL += '/';
12740 
12741 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
12742 
12743 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
12744 
12745 	// Add before unload listener
12746 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
12747 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
12748 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
12749 
12750 	// Must be on window or IE will leak if the editor is placed in frame or iframe
12751 	Event.add(window, 'beforeunload', function(e) {
12752 		tinymce.onBeforeUnload.dispatch(tinymce, e);
12753 	});
12754 
12755 	tinymce.onAddEditor = new Dispatcher(tinymce);
12756 
12757 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
12758 
12759 	tinymce.EditorManager = extend(tinymce, {
12760 		editors : [],
12761 
12762 		i18n : {},
12763 
12764 		activeEditor : null,
12765 
12766 		init : function(s) {
12767 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
12768 
12769 			function createId(elm) {
12770 				var id = elm.id;
12771 	
12772 				// Use element id, or unique name or generate a unique id
12773 				if (!id) {
12774 					id = elm.name;
12775 	
12776 					if (id && !DOM.get(id)) {
12777 						id = elm.name;
12778 					} else {
12779 						// Generate unique name
12780 						id = DOM.uniqueId();
12781 					}
12782 
12783 					elm.setAttribute('id', id);
12784 				}
12785 
12786 				return id;
12787 			};
12788 
12789 			function execCallback(se, n, s) {
12790 				var f = se[n];
12791 
12792 				if (!f)
12793 					return;
12794 
12795 				if (tinymce.is(f, 'string')) {
12796 					s = f.replace(/\.\w+$/, '');
12797 					s = s ? tinymce.resolve(s) : 0;
12798 					f = tinymce.resolve(f);
12799 				}
12800 
12801 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
12802 			};
12803 
12804 			function hasClass(n, c) {
12805 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
12806 			};
12807 
12808 			t.settings = s;
12809 
12810 			// Legacy call
12811 			Event.bind(window, 'ready', function() {
12812 				var l, co;
12813 
12814 				execCallback(s, 'onpageload');
12815 
12816 				switch (s.mode) {
12817 					case "exact":
12818 						l = s.elements || '';
12819 
12820 						if(l.length > 0) {
12821 							each(explode(l), function(v) {
12822 								if (DOM.get(v)) {
12823 									ed = new tinymce.Editor(v, s);
12824 									el.push(ed);
12825 									ed.render(1);
12826 								} else {
12827 									each(document.forms, function(f) {
12828 										each(f.elements, function(e) {
12829 											if (e.name === v) {
12830 												v = 'mce_editor_' + instanceCounter++;
12831 												DOM.setAttrib(e, 'id', v);
12832 
12833 												ed = new tinymce.Editor(v, s);
12834 												el.push(ed);
12835 												ed.render(1);
12836 											}
12837 										});
12838 									});
12839 								}
12840 							});
12841 						}
12842 						break;
12843 
12844 					case "textareas":
12845 					case "specific_textareas":
12846 						each(DOM.select('textarea'), function(elm) {
12847 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
12848 								return;
12849 
12850 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
12851 								ed = new tinymce.Editor(createId(elm), s);
12852 								el.push(ed);
12853 								ed.render(1);
12854 							}
12855 						});
12856 						break;
12857 					
12858 					default:
12859 						if (s.types) {
12860 							// Process type specific selector
12861 							each(s.types, function(type) {
12862 								each(DOM.select(type.selector), function(elm) {
12863 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
12864 									el.push(editor);
12865 									editor.render(1);
12866 								});
12867 							});
12868 						} else if (s.selector) {
12869 							// Process global selector
12870 							each(DOM.select(s.selector), function(elm) {
12871 								var editor = new tinymce.Editor(createId(elm), s);
12872 								el.push(editor);
12873 								editor.render(1);
12874 							});
12875 						}
12876 				}
12877 
12878 				// Call onInit when all editors are initialized
12879 				if (s.oninit) {
12880 					l = co = 0;
12881 
12882 					each(el, function(ed) {
12883 						co++;
12884 
12885 						if (!ed.initialized) {
12886 							// Wait for it
12887 							ed.onInit.add(function() {
12888 								l++;
12889 
12890 								// All done
12891 								if (l == co)
12892 									execCallback(s, 'oninit');
12893 							});
12894 						} else
12895 							l++;
12896 
12897 						// All done
12898 						if (l == co)
12899 							execCallback(s, 'oninit');					
12900 					});
12901 				}
12902 			});
12903 		},
12904 
12905 		get : function(id) {
12906 			if (id === undef)
12907 				return this.editors;
12908 
12909 			return this.editors[id];
12910 		},
12911 
12912 		getInstanceById : function(id) {
12913 			return this.get(id);
12914 		},
12915 
12916 		add : function(editor) {
12917 			var self = this, editors = self.editors;
12918 
12919 			// Add named and index editor instance
12920 			editors[editor.id] = editor;
12921 			editors.push(editor);
12922 
12923 			self._setActive(editor);
12924 			self.onAddEditor.dispatch(self, editor);
12925 
12926 
12927 			return editor;
12928 		},
12929 
12930 		remove : function(editor) {
12931 			var t = this, i, editors = t.editors;
12932 
12933 			// Not in the collection
12934 			if (!editors[editor.id])
12935 				return null;
12936 
12937 			delete editors[editor.id];
12938 
12939 			for (i = 0; i < editors.length; i++) {
12940 				if (editors[i] == editor) {
12941 					editors.splice(i, 1);
12942 					break;
12943 				}
12944 			}
12945 
12946 			// Select another editor since the active one was removed
12947 			if (t.activeEditor == editor)
12948 				t._setActive(editors[0]);
12949 
12950 			editor.destroy();
12951 			t.onRemoveEditor.dispatch(t, editor);
12952 
12953 			return editor;
12954 		},
12955 
12956 		execCommand : function(c, u, v) {
12957 			var t = this, ed = t.get(v), w;
12958 
12959 			function clr() {
12960 				ed.destroy();
12961 				w.detachEvent('onunload', clr);
12962 				w = w.tinyMCE = w.tinymce = null; // IE leak
12963 			};
12964 
12965 			// Manager commands
12966 			switch (c) {
12967 				case "mceFocus":
12968 					ed.focus();
12969 					return true;
12970 
12971 				case "mceAddEditor":
12972 				case "mceAddControl":
12973 					if (!t.get(v))
12974 						new tinymce.Editor(v, t.settings).render();
12975 
12976 					return true;
12977 
12978 				case "mceAddFrameControl":
12979 					w = v.window;
12980 
12981 					// Add tinyMCE global instance and tinymce namespace to specified window
12982 					w.tinyMCE = tinyMCE;
12983 					w.tinymce = tinymce;
12984 
12985 					tinymce.DOM.doc = w.document;
12986 					tinymce.DOM.win = w;
12987 
12988 					ed = new tinymce.Editor(v.element_id, v);
12989 					ed.render();
12990 
12991 					// Fix IE memory leaks
12992 					if (tinymce.isIE) {
12993 						w.attachEvent('onunload', clr);
12994 					}
12995 
12996 					v.page_window = null;
12997 
12998 					return true;
12999 
13000 				case "mceRemoveEditor":
13001 				case "mceRemoveControl":
13002 					if (ed)
13003 						ed.remove();
13004 
13005 					return true;
13006 
13007 				case 'mceToggleEditor':
13008 					if (!ed) {
13009 						t.execCommand('mceAddControl', 0, v);
13010 						return true;
13011 					}
13012 
13013 					if (ed.isHidden())
13014 						ed.show();
13015 					else
13016 						ed.hide();
13017 
13018 					return true;
13019 			}
13020 
13021 			// Run command on active editor
13022 			if (t.activeEditor)
13023 				return t.activeEditor.execCommand(c, u, v);
13024 
13025 			return false;
13026 		},
13027 
13028 		execInstanceCommand : function(id, c, u, v) {
13029 			var ed = this.get(id);
13030 
13031 			if (ed)
13032 				return ed.execCommand(c, u, v);
13033 
13034 			return false;
13035 		},
13036 
13037 		triggerSave : function() {
13038 			each(this.editors, function(e) {
13039 				e.save();
13040 			});
13041 		},
13042 
13043 		addI18n : function(p, o) {
13044 			var lo, i18n = this.i18n;
13045 
13046 			if (!tinymce.is(p, 'string')) {
13047 				each(p, function(o, lc) {
13048 					each(o, function(o, g) {
13049 						each(o, function(o, k) {
13050 							if (g === 'common')
13051 								i18n[lc + '.' + k] = o;
13052 							else
13053 								i18n[lc + '.' + g + '.' + k] = o;
13054 						});
13055 					});
13056 				});
13057 			} else {
13058 				each(o, function(o, k) {
13059 					i18n[p + '.' + k] = o;
13060 				});
13061 			}
13062 		},
13063 
13064 		// Private methods
13065 
13066 		_setActive : function(editor) {
13067 			this.selectedInstance = this.activeEditor = editor;
13068 		}
13069 	});
13070 })(tinymce);
13071 
13072 (function(tinymce) {
13073 	// Shorten these names
13074 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13075 		each = tinymce.each, isGecko = tinymce.isGecko,
13076 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13077 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13078 		explode = tinymce.explode;
13079 
13080 	tinymce.create('tinymce.Editor', {
13081 		Editor : function(id, settings) {
13082 			var self = this, TRUE = true;
13083 
13084 			self.settings = settings = extend({
13085 				id : id,
13086 				language : 'en',
13087 				theme : 'advanced',
13088 				skin : 'default',
13089 				delta_width : 0,
13090 				delta_height : 0,
13091 				popup_css : '',
13092 				plugins : '',
13093 				document_base_url : tinymce.documentBaseURL,
13094 				add_form_submit_trigger : TRUE,
13095 				submit_patch : TRUE,
13096 				add_unload_trigger : TRUE,
13097 				convert_urls : TRUE,
13098 				relative_urls : TRUE,
13099 				remove_script_host : TRUE,
13100 				table_inline_editing : false,
13101 				object_resizing : TRUE,
13102 				accessibility_focus : TRUE,
13103 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13104 				visual : TRUE,
13105 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13106 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13107 				apply_source_formatting : TRUE,
13108 				directionality : 'ltr',
13109 				forced_root_block : 'p',
13110 				hidden_input : TRUE,
13111 				padd_empty_editor : TRUE,
13112 				render_ui : TRUE,
13113 				indentation : '30px',
13114 				fix_table_elements : TRUE,
13115 				inline_styles : TRUE,
13116 				convert_fonts_to_spans : TRUE,
13117 				indent : 'simple',
13118 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13119 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13120 				validate : TRUE,
13121 				entity_encoding : 'named',
13122 				url_converter : self.convertURL,
13123 				url_converter_scope : self,
13124 				ie7_compat : TRUE
13125 			}, settings);
13126 
13127 			self.id = self.editorId = id;
13128 
13129 			self.isNotDirty = false;
13130 
13131 			self.plugins = {};
13132 
13133 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13134 				base_uri : tinyMCE.baseURI
13135 			});
13136 
13137 			self.baseURI = tinymce.baseURI;
13138 
13139 			self.contentCSS = [];
13140 
13141 			self.contentStyles = [];
13142 
13143 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13144 			self.setupEvents();
13145 
13146 			// Internal command handler objects
13147 			self.execCommands = {};
13148 			self.queryStateCommands = {};
13149 			self.queryValueCommands = {};
13150 
13151 			// Call setup
13152 			self.execCallback('setup', self);
13153 		},
13154 
13155 		render : function(nst) {
13156 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13157 
13158 			// Page is not loaded yet, wait for it
13159 			if (!Event.domLoaded) {
13160 				Event.add(window, 'ready', function() {
13161 					t.render();
13162 				});
13163 				return;
13164 			}
13165 
13166 			tinyMCE.settings = s;
13167 
13168 			// Element not found, then skip initialization
13169 			if (!t.getElement())
13170 				return;
13171 
13172 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13173 			// here since the browser says it has contentEditable support but there is no visible caret.
13174 			if (tinymce.isIDevice && !tinymce.isIOS5)
13175 				return;
13176 
13177 			// Add hidden input for non input elements inside form elements
13178 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13179 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13180 
13181 			// Hide target element early to prevent content flashing
13182 			if (!s.content_editable) {
13183 				t.orgVisibility = t.getElement().style.visibility;
13184 				t.getElement().style.visibility = 'hidden';
13185 			}
13186 
13187 			if (tinymce.WindowManager)
13188 				t.windowManager = new tinymce.WindowManager(t);
13189 
13190 			if (s.encoding == 'xml') {
13191 				t.onGetContent.add(function(ed, o) {
13192 					if (o.save)
13193 						o.content = DOM.encode(o.content);
13194 				});
13195 			}
13196 
13197 			if (s.add_form_submit_trigger) {
13198 				t.onSubmit.addToTop(function() {
13199 					if (t.initialized) {
13200 						t.save();
13201 						t.isNotDirty = 1;
13202 					}
13203 				});
13204 			}
13205 
13206 			if (s.add_unload_trigger) {
13207 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13208 					if (t.initialized && !t.destroyed && !t.isHidden())
13209 						t.save({format : 'raw', no_events : true});
13210 				});
13211 			}
13212 
13213 			tinymce.addUnload(t.destroy, t);
13214 
13215 			if (s.submit_patch) {
13216 				t.onBeforeRenderUI.add(function() {
13217 					var n = t.getElement().form;
13218 
13219 					if (!n)
13220 						return;
13221 
13222 					// Already patched
13223 					if (n._mceOldSubmit)
13224 						return;
13225 
13226 					// Check page uses id="submit" or name="submit" for it's submit button
13227 					if (!n.submit.nodeType && !n.submit.length) {
13228 						t.formElement = n;
13229 						n._mceOldSubmit = n.submit;
13230 						n.submit = function() {
13231 							// Save all instances
13232 							tinymce.triggerSave();
13233 							t.isNotDirty = 1;
13234 
13235 							return t.formElement._mceOldSubmit(t.formElement);
13236 						};
13237 					}
13238 
13239 					n = null;
13240 				});
13241 			}
13242 
13243 			// Load scripts
13244 			function loadScripts() {
13245 				if (s.language && s.language_load !== false)
13246 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13247 
13248 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13249 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13250 
13251 				each(explode(s.plugins), function(p) {
13252 					if (p &&!PluginManager.urls[p]) {
13253 						if (p.charAt(0) == '-') {
13254 							p = p.substr(1, p.length);
13255 							var dependencies = PluginManager.dependencies(p);
13256 							each(dependencies, function(dep) {
13257 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13258 								dep = PluginManager.createUrl(defaultSettings, dep);
13259 								PluginManager.load(dep.resource, dep);
13260 							});
13261 						} else {
13262 							// Skip safari plugin, since it is removed as of 3.3b1
13263 							if (p == 'safari') {
13264 								return;
13265 							}
13266 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13267 						}
13268 					}
13269 				});
13270 
13271 				// Init when que is loaded
13272 				sl.loadQueue(function() {
13273 					if (!t.removed)
13274 						t.init();
13275 				});
13276 			};
13277 
13278 			loadScripts();
13279 		},
13280 
13281 		init : function() {
13282 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13283 
13284 			tinymce.add(t);
13285 
13286 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13287 
13288 			if (s.theme) {
13289 				if (typeof s.theme != "function") {
13290 					s.theme = s.theme.replace(/-/, '');
13291 					o = ThemeManager.get(s.theme);
13292 					t.theme = new o();
13293 
13294 					if (t.theme.init)
13295 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13296 				} else {
13297 					t.theme = s.theme;
13298 				}
13299 			}
13300 
13301 			function initPlugin(p) {
13302 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13303 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13304 					each(PluginManager.dependencies(p), function(dep){
13305 						initPlugin(dep);
13306 					});
13307 					po = new c(t, u);
13308 
13309 					t.plugins[p] = po;
13310 
13311 					if (po.init) {
13312 						po.init(t, u);
13313 						initializedPlugins.push(p);
13314 					}
13315 				}
13316 			}
13317 			
13318 			// Create all plugins
13319 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13320 
13321 			// Setup popup CSS path(s)
13322 			if (s.popup_css !== false) {
13323 				if (s.popup_css)
13324 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13325 				else
13326 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13327 			}
13328 
13329 			if (s.popup_css_add)
13330 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13331 
13332 			t.controlManager = new tinymce.ControlManager(t);
13333 
13334 			// Enables users to override the control factory
13335 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
13336 
13337 			// Measure box
13338 			if (s.render_ui && t.theme) {
13339 				t.orgDisplay = e.style.display;
13340 
13341 				if (typeof s.theme != "function") {
13342 					w = s.width || e.style.width || e.offsetWidth;
13343 					h = s.height || e.style.height || e.offsetHeight;
13344 					mh = s.min_height || 100;
13345 					re = /^[0-9\.]+(|px)$/i;
13346 
13347 					if (re.test('' + w))
13348 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13349 
13350 					if (re.test('' + h))
13351 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13352 
13353 					// Render UI
13354 					o = t.theme.renderUI({
13355 						targetNode : e,
13356 						width : w,
13357 						height : h,
13358 						deltaWidth : s.delta_width,
13359 						deltaHeight : s.delta_height
13360 					});
13361 
13362 					// Resize editor
13363 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
13364 						width : w,
13365 						height : h
13366 					});
13367 
13368 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13369 					if (h < mh)
13370 						h = mh;
13371 				} else {
13372 					o = s.theme(t, e);
13373 
13374 					// Convert element type to id:s
13375 					if (o.editorContainer.nodeType) {
13376 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13377 					}
13378 
13379 					// Convert element type to id:s
13380 					if (o.iframeContainer.nodeType) {
13381 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13382 					}
13383 
13384 					// Use specified iframe height or the targets offsetHeight
13385 					h = o.iframeHeight || e.offsetHeight;
13386 
13387 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
13388 					if (isIE) {
13389 						t.onInit.add(function(ed) {
13390 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
13391 								ed.lastIERng = ed.selection.getRng();
13392 							});
13393 						});
13394 					}
13395 				}
13396 
13397 				t.editorContainer = o.editorContainer;
13398 			}
13399 
13400 			// Load specified content CSS last
13401 			if (s.content_css) {
13402 				each(explode(s.content_css), function(u) {
13403 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13404 				});
13405 			}
13406 
13407 			// Content editable mode ends here
13408 			if (s.content_editable) {
13409 				e = n = o = null; // Fix IE leak
13410 				return t.initContentBody();
13411 			}
13412 
13413 			// User specified a document.domain value
13414 			if (document.domain && location.hostname != document.domain)
13415 				tinymce.relaxedDomain = document.domain;
13416 
13417 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13418 
13419 			// We only need to override paths if we have to
13420 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
13421 			if (s.document_base_url != tinymce.documentBaseURL)
13422 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13423 
13424 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13425 			if (s.ie7_compat)
13426 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13427 			else
13428 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13429 
13430 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13431 
13432 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
13433 			for (i = 0; i < t.contentCSS.length; i++) {
13434 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13435 			}
13436 
13437 			t.contentCSS = [];
13438 
13439 			bi = s.body_id || 'tinymce';
13440 			if (bi.indexOf('=') != -1) {
13441 				bi = t.getParam('body_id', '', 'hash');
13442 				bi = bi[t.id] || bi;
13443 			}
13444 
13445 			bc = s.body_class || '';
13446 			if (bc.indexOf('=') != -1) {
13447 				bc = t.getParam('body_class', '', 'hash');
13448 				bc = bc[t.id] || '';
13449 			}
13450 
13451 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13452 
13453 			// Domain relaxing enabled, then set document domain
13454 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13455 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
13456 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13457 			}
13458 
13459 			// Create iframe
13460 			// TODO: ACC add the appropriate description on this.
13461 			n = DOM.add(o.iframeContainer, 'iframe', { 
13462 				id : t.id + "_ifr",
13463 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13464 				frameBorder : '0',
13465 				allowTransparency : "true",
13466 				title : s.aria_label,
13467 				style : {
13468 					width : '100%',
13469 					height : h,
13470 					display : 'block' // Important for Gecko to render the iframe correctly
13471 				}
13472 			});
13473 
13474 			t.contentAreaContainer = o.iframeContainer;
13475 
13476 			if (o.editorContainer) {
13477 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
13478 			}
13479 
13480 			// Restore visibility on target element
13481 			e.style.visibility = t.orgVisibility;
13482 
13483 			DOM.get(t.id).style.display = 'none';
13484 			DOM.setAttrib(t.id, 'aria-hidden', true);
13485 
13486 			if (!tinymce.relaxedDomain || !u)
13487 				t.initContentBody();
13488 
13489 			e = n = o = null; // Cleanup
13490 		},
13491 
13492 		initContentBody : function() {
13493 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13494 
13495 			// Setup iframe body
13496 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13497 				doc.open();
13498 				doc.write(self.iframeHTML);
13499 				doc.close();
13500 
13501 				if (tinymce.relaxedDomain)
13502 					doc.domain = tinymce.relaxedDomain;
13503 			}
13504 
13505 			if (settings.content_editable) {
13506 				DOM.addClass(targetElm, 'mceContentBody');
13507 				self.contentDocument = doc = settings.content_document || document;
13508 				self.contentWindow = settings.content_window || window;
13509 				self.bodyElement = targetElm;
13510 
13511 				// Prevent leak in IE
13512 				settings.content_document = settings.content_window = null;
13513 			}
13514 
13515 			// It will not steal focus while setting contentEditable
13516 			body = self.getBody();
13517 			body.disabled = true;
13518 
13519 			if (!settings.readonly)
13520 				body.contentEditable = self.getParam('content_editable_state', true);
13521 
13522 			body.disabled = false;
13523 
13524 			self.schema = new tinymce.html.Schema(settings);
13525 
13526 			self.dom = new tinymce.dom.DOMUtils(doc, {
13527 				keep_values : true,
13528 				url_converter : self.convertURL,
13529 				url_converter_scope : self,
13530 				hex_colors : settings.force_hex_style_colors,
13531 				class_filter : settings.class_filter,
13532 				update_styles : true,
13533 				root_element : settings.content_editable ? self.id : null,
13534 				schema : self.schema
13535 			});
13536 
13537 			self.parser = new tinymce.html.DomParser(settings, self.schema);
13538 
13539 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
13540 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13541 				var i = nodes.length, node, dom = self.dom, value, internalName;
13542 
13543 				while (i--) {
13544 					node = nodes[i];
13545 					value = node.attr(name);
13546 					internalName = 'data-mce-' + name;
13547 
13548 					// Add internal attribute if we need to we don't on a refresh of the document
13549 					if (!node.attributes.map[internalName]) {	
13550 						if (name === "style")
13551 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13552 						else
13553 							node.attr(internalName, self.convertURL(value, name, node.name));
13554 					}
13555 				}
13556 			});
13557 
13558 			// Keep scripts from executing
13559 			self.parser.addNodeFilter('script', function(nodes, name) {
13560 				var i = nodes.length, node;
13561 
13562 				while (i--) {
13563 					node = nodes[i];
13564 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13565 				}
13566 			});
13567 
13568 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
13569 				var i = nodes.length, node;
13570 
13571 				while (i--) {
13572 					node = nodes[i];
13573 					node.type = 8;
13574 					node.name = '#comment';
13575 					node.value = '[CDATA[' + node.value + ']]';
13576 				}
13577 			});
13578 
13579 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13580 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13581 
13582 				while (i--) {
13583 					node = nodes[i];
13584 
13585 					if (node.isEmpty(nonEmptyElements))
13586 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13587 				}
13588 			});
13589 
13590 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13591 
13592 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13593 
13594 			self.formatter = new tinymce.Formatter(self);
13595 
13596 			self.undoManager = new tinymce.UndoManager(self);
13597 
13598 			self.forceBlocks = new tinymce.ForceBlocks(self);
13599 			self.enterKey = new tinymce.EnterKey(self);
13600 			self.editorCommands = new tinymce.EditorCommands(self);
13601 
13602 			self.onExecCommand.add(function(editor, command) {
13603 				// Don't refresh the select lists until caret move
13604 				if (!/^(FontName|FontSize)$/.test(command))
13605 					self.nodeChanged();
13606 			});
13607 
13608 			// Pass through
13609 			self.serializer.onPreProcess.add(function(se, o) {
13610 				return self.onPreProcess.dispatch(self, o, se);
13611 			});
13612 
13613 			self.serializer.onPostProcess.add(function(se, o) {
13614 				return self.onPostProcess.dispatch(self, o, se);
13615 			});
13616 
13617 			self.onPreInit.dispatch(self);
13618 
13619 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13620 				doc.body.spellcheck = false;
13621 
13622 			if (!settings.readonly) {
13623 				self.bindNativeEvents();
13624 			}
13625 
13626 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
13627 			self.onPostRender.dispatch(self);
13628 
13629 			self.quirks = tinymce.util.Quirks(self);
13630 
13631 			if (settings.directionality)
13632 				body.dir = settings.directionality;
13633 
13634 			if (settings.nowrap)
13635 				body.style.whiteSpace = "nowrap";
13636 
13637 			if (settings.protect) {
13638 				self.onBeforeSetContent.add(function(ed, o) {
13639 					each(settings.protect, function(pattern) {
13640 						o.content = o.content.replace(pattern, function(str) {
13641 							return '<!--mce:protected ' + escape(str) + '-->';
13642 						});
13643 					});
13644 				});
13645 			}
13646 
13647 			// Add visual aids when new contents is added
13648 			self.onSetContent.add(function() {
13649 				self.addVisual(self.getBody());
13650 			});
13651 
13652 			// Remove empty contents
13653 			if (settings.padd_empty_editor) {
13654 				self.onPostProcess.add(function(ed, o) {
13655 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13656 				});
13657 			}
13658 
13659 			self.load({initial : true, format : 'html'});
13660 			self.startContent = self.getContent({format : 'raw'});
13661 
13662 			self.initialized = true;
13663 
13664 			self.onInit.dispatch(self);
13665 			self.execCallback('setupcontent_callback', self.id, body, doc);
13666 			self.execCallback('init_instance_callback', self);
13667 			self.focus(true);
13668 			self.nodeChanged({initial : true});
13669 
13670 			// Add editor specific CSS styles
13671 			if (self.contentStyles.length > 0) {
13672 				contentCssText = '';
13673 
13674 				each(self.contentStyles, function(style) {
13675 					contentCssText += style + "\r\n";
13676 				});
13677 
13678 				self.dom.addStyle(contentCssText);
13679 			}
13680 
13681 			// Load specified content CSS last
13682 			each(self.contentCSS, function(url) {
13683 				self.dom.loadCSS(url);
13684 			});
13685 
13686 			// Handle auto focus
13687 			if (settings.auto_focus) {
13688 				setTimeout(function () {
13689 					var ed = tinymce.get(settings.auto_focus);
13690 
13691 					ed.selection.select(ed.getBody(), 1);
13692 					ed.selection.collapse(1);
13693 					ed.getBody().focus();
13694 					ed.getWin().focus();
13695 				}, 100);
13696 			}
13697 
13698 			// Clean up references for IE
13699 			targetElm = doc = body = null;
13700 		},
13701 
13702 		focus : function(skip_focus) {
13703 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
13704 
13705 			if (!skip_focus) {
13706 				if (self.lastIERng) {
13707 					selection.setRng(self.lastIERng);
13708 				}
13709 
13710 				// Get selected control element
13711 				ieRng = selection.getRng();
13712 				if (ieRng.item) {
13713 					controlElm = ieRng.item(0);
13714 				}
13715 
13716 				self._refreshContentEditable();
13717 
13718 				// Focus the window iframe
13719 				if (!contentEditable) {
13720 					self.getWin().focus();
13721 				}
13722 
13723 				// Focus the body as well since it's contentEditable
13724 				if (tinymce.isGecko || contentEditable) {
13725 					body = self.getBody();
13726 
13727 					// Check for setActive since it doesn't scroll to the element
13728 					if (body.setActive) {
13729 						body.setActive();
13730 					} else {
13731 						body.focus();
13732 					}
13733 
13734 					if (contentEditable) {
13735 						selection.normalize();
13736 					}
13737 				}
13738 
13739 				// Restore selected control element
13740 				// This is needed when for example an image is selected within a
13741 				// layer a call to focus will then remove the control selection
13742 				if (controlElm && controlElm.ownerDocument == doc) {
13743 					ieRng = doc.body.createControlRange();
13744 					ieRng.addElement(controlElm);
13745 					ieRng.select();
13746 				}
13747 			}
13748 
13749 			if (tinymce.activeEditor != self) {
13750 				if ((oed = tinymce.activeEditor) != null)
13751 					oed.onDeactivate.dispatch(oed, self);
13752 
13753 				self.onActivate.dispatch(self, oed);
13754 			}
13755 
13756 			tinymce._setActive(self);
13757 		},
13758 
13759 		execCallback : function(n) {
13760 			var t = this, f = t.settings[n], s;
13761 
13762 			if (!f)
13763 				return;
13764 
13765 			// Look through lookup
13766 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
13767 				f = s.func;
13768 				s = s.scope;
13769 			}
13770 
13771 			if (is(f, 'string')) {
13772 				s = f.replace(/\.\w+$/, '');
13773 				s = s ? tinymce.resolve(s) : 0;
13774 				f = tinymce.resolve(f);
13775 				t.callbackLookup = t.callbackLookup || {};
13776 				t.callbackLookup[n] = {func : f, scope : s};
13777 			}
13778 
13779 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
13780 		},
13781 
13782 		translate : function(s) {
13783 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
13784 
13785 			if (!s)
13786 				return '';
13787 
13788 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
13789 				return i18n[c + '.' + b] || '{#' + b + '}';
13790 			});
13791 		},
13792 
13793 		getLang : function(n, dv) {
13794 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
13795 		},
13796 
13797 		getParam : function(n, dv, ty) {
13798 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
13799 
13800 			if (ty === 'hash') {
13801 				o = {};
13802 
13803 				if (is(v, 'string')) {
13804 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
13805 						v = v.split('=');
13806 
13807 						if (v.length > 1)
13808 							o[tr(v[0])] = tr(v[1]);
13809 						else
13810 							o[tr(v[0])] = tr(v);
13811 					});
13812 				} else
13813 					o = v;
13814 
13815 				return o;
13816 			}
13817 
13818 			return v;
13819 		},
13820 
13821 		nodeChanged : function(o) {
13822 			var self = this, selection = self.selection, node;
13823 
13824 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
13825 			if (self.initialized) {
13826 				o = o || {};
13827 
13828 				// Get start node
13829 				node = selection.getStart() || self.getBody();
13830 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
13831 
13832 				// Get parents and add them to object
13833 				o.parents = [];
13834 				self.dom.getParent(node, function(node) {
13835 					if (node.nodeName == 'BODY')
13836 						return true;
13837 
13838 					o.parents.push(node);
13839 				});
13840 
13841 				self.onNodeChange.dispatch(
13842 					self,
13843 					o ? o.controlManager || self.controlManager : self.controlManager,
13844 					node,
13845 					selection.isCollapsed(),
13846 					o
13847 				);
13848 			}
13849 		},
13850 
13851 		addButton : function(name, settings) {
13852 			var self = this;
13853 
13854 			self.buttons = self.buttons || {};
13855 			self.buttons[name] = settings;
13856 		},
13857 
13858 		addCommand : function(name, callback, scope) {
13859 			this.execCommands[name] = {func : callback, scope : scope || this};
13860 		},
13861 
13862 		addQueryStateHandler : function(name, callback, scope) {
13863 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
13864 		},
13865 
13866 		addQueryValueHandler : function(name, callback, scope) {
13867 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
13868 		},
13869 
13870 		addShortcut : function(pa, desc, cmd_func, sc) {
13871 			var t = this, c;
13872 
13873 			if (t.settings.custom_shortcuts === false)
13874 				return false;
13875 
13876 			t.shortcuts = t.shortcuts || {};
13877 
13878 			if (is(cmd_func, 'string')) {
13879 				c = cmd_func;
13880 
13881 				cmd_func = function() {
13882 					t.execCommand(c, false, null);
13883 				};
13884 			}
13885 
13886 			if (is(cmd_func, 'object')) {
13887 				c = cmd_func;
13888 
13889 				cmd_func = function() {
13890 					t.execCommand(c[0], c[1], c[2]);
13891 				};
13892 			}
13893 
13894 			each(explode(pa), function(pa) {
13895 				var o = {
13896 					func : cmd_func,
13897 					scope : sc || this,
13898 					desc : t.translate(desc),
13899 					alt : false,
13900 					ctrl : false,
13901 					shift : false
13902 				};
13903 
13904 				each(explode(pa, '+'), function(v) {
13905 					switch (v) {
13906 						case 'alt':
13907 						case 'ctrl':
13908 						case 'shift':
13909 							o[v] = true;
13910 							break;
13911 
13912 						default:
13913 							o.charCode = v.charCodeAt(0);
13914 							o.keyCode = v.toUpperCase().charCodeAt(0);
13915 					}
13916 				});
13917 
13918 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
13919 			});
13920 
13921 			return true;
13922 		},
13923 
13924 		execCommand : function(cmd, ui, val, a) {
13925 			var t = this, s = 0, o, st;
13926 
13927 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
13928 				t.focus();
13929 
13930 			a = extend({}, a);
13931 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
13932 			if (a.terminate)
13933 				return false;
13934 
13935 			// Command callback
13936 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
13937 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13938 				return true;
13939 			}
13940 
13941 			// Registred commands
13942 			if (o = t.execCommands[cmd]) {
13943 				st = o.func.call(o.scope, ui, val);
13944 
13945 				// Fall through on true
13946 				if (st !== true) {
13947 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13948 					return st;
13949 				}
13950 			}
13951 
13952 			// Plugin commands
13953 			each(t.plugins, function(p) {
13954 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
13955 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
13956 					s = 1;
13957 					return false;
13958 				}
13959 			});
13960 
13961 			if (s)
13962 				return true;
13963 
13964 			// Theme commands
13965 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
13966 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13967 				return true;
13968 			}
13969 
13970 			// Editor commands
13971 			if (t.editorCommands.execCommand(cmd, ui, val)) {
13972 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
13973 				return true;
13974 			}
13975 
13976 			// Browser commands
13977 			t.getDoc().execCommand(cmd, ui, val);
13978 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
13979 		},
13980 
13981 		queryCommandState : function(cmd) {
13982 			var t = this, o, s;
13983 
13984 			// Is hidden then return undefined
13985 			if (t._isHidden())
13986 				return;
13987 
13988 			// Registred commands
13989 			if (o = t.queryStateCommands[cmd]) {
13990 				s = o.func.call(o.scope);
13991 
13992 				// Fall though on true
13993 				if (s !== true)
13994 					return s;
13995 			}
13996 
13997 			// Registred commands
13998 			o = t.editorCommands.queryCommandState(cmd);
13999 			if (o !== -1)
14000 				return o;
14001 
14002 			// Browser commands
14003 			try {
14004 				return this.getDoc().queryCommandState(cmd);
14005 			} catch (ex) {
14006 				// Fails sometimes see bug: 1896577
14007 			}
14008 		},
14009 
14010 		queryCommandValue : function(c) {
14011 			var t = this, o, s;
14012 
14013 			// Is hidden then return undefined
14014 			if (t._isHidden())
14015 				return;
14016 
14017 			// Registred commands
14018 			if (o = t.queryValueCommands[c]) {
14019 				s = o.func.call(o.scope);
14020 
14021 				// Fall though on true
14022 				if (s !== true)
14023 					return s;
14024 			}
14025 
14026 			// Registred commands
14027 			o = t.editorCommands.queryCommandValue(c);
14028 			if (is(o))
14029 				return o;
14030 
14031 			// Browser commands
14032 			try {
14033 				return this.getDoc().queryCommandValue(c);
14034 			} catch (ex) {
14035 				// Fails sometimes see bug: 1896577
14036 			}
14037 		},
14038 
14039 		show : function() {
14040 			var self = this;
14041 
14042 			DOM.show(self.getContainer());
14043 			DOM.hide(self.id);
14044 			self.load();
14045 		},
14046 
14047 		hide : function() {
14048 			var self = this, doc = self.getDoc();
14049 
14050 			// Fixed bug where IE has a blinking cursor left from the editor
14051 			if (isIE && doc)
14052 				doc.execCommand('SelectAll');
14053 
14054 			// We must save before we hide so Safari doesn't crash
14055 			self.save();
14056 			DOM.hide(self.getContainer());
14057 			DOM.setStyle(self.id, 'display', self.orgDisplay);
14058 		},
14059 
14060 		isHidden : function() {
14061 			return !DOM.isHidden(this.id);
14062 		},
14063 
14064 		setProgressState : function(b, ti, o) {
14065 			this.onSetProgressState.dispatch(this, b, ti, o);
14066 
14067 			return b;
14068 		},
14069 
14070 		load : function(o) {
14071 			var t = this, e = t.getElement(), h;
14072 
14073 			if (e) {
14074 				o = o || {};
14075 				o.load = true;
14076 
14077 				// Double encode existing entities in the value
14078 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14079 				o.element = e;
14080 
14081 				if (!o.no_events)
14082 					t.onLoadContent.dispatch(t, o);
14083 
14084 				o.element = e = null;
14085 
14086 				return h;
14087 			}
14088 		},
14089 
14090 		save : function(o) {
14091 			var t = this, e = t.getElement(), h, f;
14092 
14093 			if (!e || !t.initialized)
14094 				return;
14095 
14096 			o = o || {};
14097 			o.save = true;
14098 
14099 			o.element = e;
14100 			h = o.content = t.getContent(o);
14101 
14102 			if (!o.no_events)
14103 				t.onSaveContent.dispatch(t, o);
14104 
14105 			h = o.content;
14106 
14107 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14108 				e.innerHTML = h;
14109 
14110 				// Update hidden form element
14111 				if (f = DOM.getParent(t.id, 'form')) {
14112 					each(f.elements, function(e) {
14113 						if (e.name == t.id) {
14114 							e.value = h;
14115 							return false;
14116 						}
14117 					});
14118 				}
14119 			} else
14120 				e.value = h;
14121 
14122 			o.element = e = null;
14123 
14124 			return h;
14125 		},
14126 
14127 		setContent : function(content, args) {
14128 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14129 
14130 			// Setup args object
14131 			args = args || {};
14132 			args.format = args.format || 'html';
14133 			args.set = true;
14134 			args.content = content;
14135 
14136 			// Do preprocessing
14137 			if (!args.no_events)
14138 				self.onBeforeSetContent.dispatch(self, args);
14139 
14140 			content = args.content;
14141 
14142 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14143 			// It will also be impossible to place the caret in the editor unless there is a BR element present
14144 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14145 				forcedRootBlockName = self.settings.forced_root_block;
14146 				if (forcedRootBlockName)
14147 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14148 				else
14149 					content = '<br data-mce-bogus="1">';
14150 
14151 				body.innerHTML = content;
14152 				self.selection.select(body, true);
14153 				self.selection.collapse(true);
14154 				return;
14155 			}
14156 
14157 			// Parse and serialize the html
14158 			if (args.format !== 'raw') {
14159 				content = new tinymce.html.Serializer({}, self.schema).serialize(
14160 					self.parser.parse(content)
14161 				);
14162 			}
14163 
14164 			// Set the new cleaned contents to the editor
14165 			args.content = tinymce.trim(content);
14166 			self.dom.setHTML(body, args.content);
14167 
14168 			// Do post processing
14169 			if (!args.no_events)
14170 				self.onSetContent.dispatch(self, args);
14171 
14172 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14173 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14174 				self.selection.normalize();
14175 			}
14176 
14177 			return args.content;
14178 		},
14179 
14180 		getContent : function(args) {
14181 			var self = this, content;
14182 
14183 			// Setup args object
14184 			args = args || {};
14185 			args.format = args.format || 'html';
14186 			args.get = true;
14187 			args.getInner = true;
14188 
14189 			// Do preprocessing
14190 			if (!args.no_events)
14191 				self.onBeforeGetContent.dispatch(self, args);
14192 
14193 			// Get raw contents or by default the cleaned contents
14194 			if (args.format == 'raw')
14195 				content = self.getBody().innerHTML;
14196 			else
14197 				content = self.serializer.serialize(self.getBody(), args);
14198 
14199 			args.content = tinymce.trim(content);
14200 
14201 			// Do post processing
14202 			if (!args.no_events)
14203 				self.onGetContent.dispatch(self, args);
14204 
14205 			return args.content;
14206 		},
14207 
14208 		isDirty : function() {
14209 			var self = this;
14210 
14211 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14212 		},
14213 
14214 		getContainer : function() {
14215 			var self = this;
14216 
14217 			if (!self.container)
14218 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
14219 
14220 			return self.container;
14221 		},
14222 
14223 		getContentAreaContainer : function() {
14224 			return this.contentAreaContainer;
14225 		},
14226 
14227 		getElement : function() {
14228 			return DOM.get(this.settings.content_element || this.id);
14229 		},
14230 
14231 		getWin : function() {
14232 			var self = this, elm;
14233 
14234 			if (!self.contentWindow) {
14235 				elm = DOM.get(self.id + "_ifr");
14236 
14237 				if (elm)
14238 					self.contentWindow = elm.contentWindow;
14239 			}
14240 
14241 			return self.contentWindow;
14242 		},
14243 
14244 		getDoc : function() {
14245 			var self = this, win;
14246 
14247 			if (!self.contentDocument) {
14248 				win = self.getWin();
14249 
14250 				if (win)
14251 					self.contentDocument = win.document;
14252 			}
14253 
14254 			return self.contentDocument;
14255 		},
14256 
14257 		getBody : function() {
14258 			return this.bodyElement || this.getDoc().body;
14259 		},
14260 
14261 		convertURL : function(url, name, elm) {
14262 			var self = this, settings = self.settings;
14263 
14264 			// Use callback instead
14265 			if (settings.urlconverter_callback)
14266 				return self.execCallback('urlconverter_callback', url, elm, true, name);
14267 
14268 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14269 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14270 				return url;
14271 
14272 			// Convert to relative
14273 			if (settings.relative_urls)
14274 				return self.documentBaseURI.toRelative(url);
14275 
14276 			// Convert to absolute
14277 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14278 
14279 			return url;
14280 		},
14281 
14282 		addVisual : function(elm) {
14283 			var self = this, settings = self.settings, dom = self.dom, cls;
14284 
14285 			elm = elm || self.getBody();
14286 
14287 			if (!is(self.hasVisual))
14288 				self.hasVisual = settings.visual;
14289 
14290 			each(dom.select('table,a', elm), function(elm) {
14291 				var value;
14292 
14293 				switch (elm.nodeName) {
14294 					case 'TABLE':
14295 						cls = settings.visual_table_class || 'mceItemTable';
14296 						value = dom.getAttrib(elm, 'border');
14297 
14298 						if (!value || value == '0') {
14299 							if (self.hasVisual)
14300 								dom.addClass(elm, cls);
14301 							else
14302 								dom.removeClass(elm, cls);
14303 						}
14304 
14305 						return;
14306 
14307 					case 'A':
14308 						if (!dom.getAttrib(elm, 'href', false)) {
14309 							value = dom.getAttrib(elm, 'name') || elm.id;
14310 							cls = 'mceItemAnchor';
14311 
14312 							if (value) {
14313 								if (self.hasVisual)
14314 									dom.addClass(elm, cls);
14315 								else
14316 									dom.removeClass(elm, cls);
14317 							}
14318 						}
14319 
14320 						return;
14321 				}
14322 			});
14323 
14324 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
14325 		},
14326 
14327 		remove : function() {
14328 			var self = this, elm = self.getContainer();
14329 
14330 			if (!self.removed) {
14331 				self.removed = 1; // Cancels post remove event execution
14332 				self.hide();
14333 
14334 				// Don't clear the window or document if content editable
14335 				// is enabled since other instances might still be present
14336 				if (!self.settings.content_editable) {
14337 					Event.unbind(self.getWin());
14338 					Event.unbind(self.getDoc());
14339 				}
14340 
14341 				Event.unbind(self.getBody());
14342 				Event.clear(elm);
14343 
14344 				self.execCallback('remove_instance_callback', self);
14345 				self.onRemove.dispatch(self);
14346 
14347 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14348 				self.onExecCommand.listeners = [];
14349 
14350 				tinymce.remove(self);
14351 				DOM.remove(elm);
14352 			}
14353 		},
14354 
14355 		destroy : function(s) {
14356 			var t = this;
14357 
14358 			// One time is enough
14359 			if (t.destroyed)
14360 				return;
14361 
14362 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14363 			if (isGecko) {
14364 				Event.unbind(t.getDoc());
14365 				Event.unbind(t.getWin());
14366 				Event.unbind(t.getBody());
14367 			}
14368 
14369 			if (!s) {
14370 				tinymce.removeUnload(t.destroy);
14371 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14372 
14373 				// Manual destroy
14374 				if (t.theme && t.theme.destroy)
14375 					t.theme.destroy();
14376 
14377 				// Destroy controls, selection and dom
14378 				t.controlManager.destroy();
14379 				t.selection.destroy();
14380 				t.dom.destroy();
14381 			}
14382 
14383 			if (t.formElement) {
14384 				t.formElement.submit = t.formElement._mceOldSubmit;
14385 				t.formElement._mceOldSubmit = null;
14386 			}
14387 
14388 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14389 
14390 			if (t.selection)
14391 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14392 
14393 			t.destroyed = 1;
14394 		},
14395 
14396 		// Internal functions
14397 
14398 		_refreshContentEditable : function() {
14399 			var self = this, body, parent;
14400 
14401 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14402 			if (self._isHidden()) {
14403 				body = self.getBody();
14404 				parent = body.parentNode;
14405 
14406 				parent.removeChild(body);
14407 				parent.appendChild(body);
14408 
14409 				body.focus();
14410 			}
14411 		},
14412 
14413 		_isHidden : function() {
14414 			var s;
14415 
14416 			if (!isGecko)
14417 				return 0;
14418 
14419 			// Weird, wheres that cursor selection?
14420 			s = this.selection.getSel();
14421 			return (!s || !s.rangeCount || s.rangeCount === 0);
14422 		}
14423 	});
14424 })(tinymce);
14425 (function(tinymce) {
14426 	var each = tinymce.each;
14427 
14428 	tinymce.Editor.prototype.setupEvents = function() {
14429 		var self = this, settings = self.settings;
14430 
14431 		// Add events to the editor
14432 		each([
14433 			'onPreInit',
14434 
14435 			'onBeforeRenderUI',
14436 
14437 			'onPostRender',
14438 
14439 			'onLoad',
14440 
14441 			'onInit',
14442 
14443 			'onRemove',
14444 
14445 			'onActivate',
14446 
14447 			'onDeactivate',
14448 
14449 			'onClick',
14450 
14451 			'onEvent',
14452 
14453 			'onMouseUp',
14454 
14455 			'onMouseDown',
14456 
14457 			'onDblClick',
14458 
14459 			'onKeyDown',
14460 
14461 			'onKeyUp',
14462 
14463 			'onKeyPress',
14464 
14465 			'onContextMenu',
14466 
14467 			'onSubmit',
14468 
14469 			'onReset',
14470 
14471 			'onPaste',
14472 
14473 			'onPreProcess',
14474 
14475 			'onPostProcess',
14476 
14477 			'onBeforeSetContent',
14478 
14479 			'onBeforeGetContent',
14480 
14481 			'onSetContent',
14482 
14483 			'onGetContent',
14484 
14485 			'onLoadContent',
14486 
14487 			'onSaveContent',
14488 
14489 			'onNodeChange',
14490 
14491 			'onChange',
14492 
14493 			'onBeforeExecCommand',
14494 
14495 			'onExecCommand',
14496 
14497 			'onUndo',
14498 
14499 			'onRedo',
14500 
14501 			'onVisualAid',
14502 
14503 			'onSetProgressState',
14504 
14505 			'onSetAttrib'
14506 		], function(name) {
14507 			self[name] = new tinymce.util.Dispatcher(self);
14508 		});
14509 
14510 		// Handle legacy cleanup_callback option
14511 		if (settings.cleanup_callback) {
14512 			self.onBeforeSetContent.add(function(ed, o) {
14513 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14514 			});
14515 
14516 			self.onPreProcess.add(function(ed, o) {
14517 				if (o.set)
14518 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14519 
14520 				if (o.get)
14521 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14522 			});
14523 
14524 			self.onPostProcess.add(function(ed, o) {
14525 				if (o.set)
14526 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14527 
14528 				if (o.get)						
14529 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14530 			});
14531 		}
14532 
14533 		// Handle legacy save_callback option
14534 		if (settings.save_callback) {
14535 			self.onGetContent.add(function(ed, o) {
14536 				if (o.save)
14537 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14538 			});
14539 		}
14540 
14541 		// Handle legacy handle_event_callback option
14542 		if (settings.handle_event_callback) {
14543 			self.onEvent.add(function(ed, e, o) {
14544 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14545 					e.preventDefault();
14546 					e.stopPropagation();
14547 				}
14548 			});
14549 		}
14550 
14551 		// Handle legacy handle_node_change_callback option
14552 		if (settings.handle_node_change_callback) {
14553 			self.onNodeChange.add(function(ed, cm, n) {
14554 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14555 			});
14556 		}
14557 
14558 		// Handle legacy save_callback option
14559 		if (settings.save_callback) {
14560 			self.onSaveContent.add(function(ed, o) {
14561 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14562 
14563 				if (h)
14564 					o.content = h;
14565 			});
14566 		}
14567 
14568 		// Handle legacy onchange_callback option
14569 		if (settings.onchange_callback) {
14570 			self.onChange.add(function(ed, l) {
14571 				ed.execCallback('onchange_callback', ed, l);
14572 			});
14573 		}
14574 	};
14575 
14576 	tinymce.Editor.prototype.bindNativeEvents = function() {
14577 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14578 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14579 
14580 		nativeToDispatcherMap = {
14581 			mouseup : 'onMouseUp',
14582 			mousedown : 'onMouseDown',
14583 			click : 'onClick',
14584 			keyup : 'onKeyUp',
14585 			keydown : 'onKeyDown',
14586 			keypress : 'onKeyPress',
14587 			submit : 'onSubmit',
14588 			reset : 'onReset',
14589 			contextmenu : 'onContextMenu',
14590 			dblclick : 'onDblClick',
14591 			paste : 'onPaste' // Doesn't work in all browsers yet
14592 		};
14593 
14594 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14595 		function eventHandler(evt, args) {
14596 			var type = evt.type;
14597 
14598 			// Don't fire events when it's removed
14599 			if (self.removed)
14600 				return;
14601 
14602 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
14603 			if (self.onEvent.dispatch(self, evt, args) !== false) {
14604 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14605 			}
14606 		};
14607 
14608 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
14609 		function doOperaFocus(e) {
14610 			self.focus(true);
14611 		};
14612 
14613 		function nodeChanged(ed, e) {
14614 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14615 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14616 				self.selection.normalize();
14617 			}
14618 
14619 			self.nodeChanged();
14620 		}
14621 
14622 		// Add DOM events
14623 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14624 			var root = settings.content_editable ? self.getBody() : self.getDoc();
14625 
14626 			switch (nativeName) {
14627 				case 'contextmenu':
14628 					dom.bind(root, nativeName, eventHandler);
14629 					break;
14630 
14631 				case 'paste':
14632 					dom.bind(self.getBody(), nativeName, eventHandler);
14633 					break;
14634 
14635 				case 'submit':
14636 				case 'reset':
14637 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14638 					break;
14639 
14640 				default:
14641 					dom.bind(root, nativeName, eventHandler);
14642 			}
14643 		});
14644 
14645 		// Set the editor as active when focused
14646 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14647 			self.focus(true);
14648 		});
14649 
14650 		if (settings.content_editable && tinymce.isOpera) {
14651 			dom.bind(self.getBody(), 'click', doOperaFocus);
14652 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
14653 		}
14654 
14655 		// Add node change handler
14656 		self.onMouseUp.add(nodeChanged);
14657 
14658 		self.onKeyUp.add(function(ed, e) {
14659 			var keyCode = e.keyCode;
14660 
14661 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14662 				nodeChanged(ed, e);
14663 		});
14664 
14665 		// Add reset handler
14666 		self.onReset.add(function() {
14667 			self.setContent(self.startContent, {format : 'raw'});
14668 		});
14669 
14670 		// Add shortcuts
14671 		function handleShortcut(e, execute) {
14672 			if (e.altKey || e.ctrlKey || e.metaKey) {
14673 				each(self.shortcuts, function(shortcut) {
14674 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
14675 
14676 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
14677 						return;
14678 
14679 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
14680 						e.preventDefault();
14681 
14682 						if (execute) {
14683 							shortcut.func.call(shortcut.scope);
14684 						}
14685 
14686 						return true;
14687 					}
14688 				});
14689 			}
14690 		};
14691 
14692 		self.onKeyUp.add(function(ed, e) {
14693 			handleShortcut(e);
14694 		});
14695 
14696 		self.onKeyPress.add(function(ed, e) {
14697 			handleShortcut(e);
14698 		});
14699 
14700 		self.onKeyDown.add(function(ed, e) {
14701 			handleShortcut(e, true);
14702 		});
14703 
14704 		if (tinymce.isOpera) {
14705 			self.onClick.add(function(ed, e) {
14706 				e.preventDefault();
14707 			});
14708 		}
14709 	};
14710 })(tinymce);
14711 (function(tinymce) {
14712 	// Added for compression purposes
14713 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
14714 
14715 	tinymce.EditorCommands = function(editor) {
14716 		var dom = editor.dom,
14717 			selection = editor.selection,
14718 			commands = {state: {}, exec : {}, value : {}},
14719 			settings = editor.settings,
14720 			formatter = editor.formatter,
14721 			bookmark;
14722 
14723 		function execCommand(command, ui, value) {
14724 			var func;
14725 
14726 			command = command.toLowerCase();
14727 			if (func = commands.exec[command]) {
14728 				func(command, ui, value);
14729 				return TRUE;
14730 			}
14731 
14732 			return FALSE;
14733 		};
14734 
14735 		function queryCommandState(command) {
14736 			var func;
14737 
14738 			command = command.toLowerCase();
14739 			if (func = commands.state[command])
14740 				return func(command);
14741 
14742 			return -1;
14743 		};
14744 
14745 		function queryCommandValue(command) {
14746 			var func;
14747 
14748 			command = command.toLowerCase();
14749 			if (func = commands.value[command])
14750 				return func(command);
14751 
14752 			return FALSE;
14753 		};
14754 
14755 		function addCommands(command_list, type) {
14756 			type = type || 'exec';
14757 
14758 			each(command_list, function(callback, command) {
14759 				each(command.toLowerCase().split(','), function(command) {
14760 					commands[type][command] = callback;
14761 				});
14762 			});
14763 		};
14764 
14765 		// Expose public methods
14766 		tinymce.extend(this, {
14767 			execCommand : execCommand,
14768 			queryCommandState : queryCommandState,
14769 			queryCommandValue : queryCommandValue,
14770 			addCommands : addCommands
14771 		});
14772 
14773 		// Private methods
14774 
14775 		function execNativeCommand(command, ui, value) {
14776 			if (ui === undef)
14777 				ui = FALSE;
14778 
14779 			if (value === undef)
14780 				value = null;
14781 
14782 			return editor.getDoc().execCommand(command, ui, value);
14783 		};
14784 
14785 		function isFormatMatch(name) {
14786 			return formatter.match(name);
14787 		};
14788 
14789 		function toggleFormat(name, value) {
14790 			formatter.toggle(name, value ? {value : value} : undef);
14791 		};
14792 
14793 		function storeSelection(type) {
14794 			bookmark = selection.getBookmark(type);
14795 		};
14796 
14797 		function restoreSelection() {
14798 			selection.moveToBookmark(bookmark);
14799 		};
14800 
14801 		// Add execCommand overrides
14802 		addCommands({
14803 			// Ignore these, added for compatibility
14804 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
14805 
14806 			// Add undo manager logic
14807 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
14808 				editor.undoManager.add();
14809 			},
14810 
14811 			'Cut,Copy,Paste' : function(command) {
14812 				var doc = editor.getDoc(), failed;
14813 
14814 				// Try executing the native command
14815 				try {
14816 					execNativeCommand(command);
14817 				} catch (ex) {
14818 					// Command failed
14819 					failed = TRUE;
14820 				}
14821 
14822 				// Present alert message about clipboard access not being available
14823 				if (failed || !doc.queryCommandSupported(command)) {
14824 					if (tinymce.isGecko) {
14825 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
14826 							if (state)
14827 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
14828 						});
14829 					} else
14830 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
14831 				}
14832 			},
14833 
14834 			// Override unlink command
14835 			unlink : function(command) {
14836 				if (selection.isCollapsed())
14837 					selection.select(selection.getNode());
14838 
14839 				execNativeCommand(command);
14840 				selection.collapse(FALSE);
14841 			},
14842 
14843 			// Override justify commands to use the text formatter engine
14844 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
14845 				var align = command.substring(7);
14846 
14847 				// Remove all other alignments first
14848 				each('left,center,right,full'.split(','), function(name) {
14849 					if (align != name)
14850 						formatter.remove('align' + name);
14851 				});
14852 
14853 				toggleFormat('align' + align);
14854 				execCommand('mceRepaint');
14855 			},
14856 
14857 			// Override list commands to fix WebKit bug
14858 			'InsertUnorderedList,InsertOrderedList' : function(command) {
14859 				var listElm, listParent;
14860 
14861 				execNativeCommand(command);
14862 
14863 				// WebKit produces lists within block elements so we need to split them
14864 				// we will replace the native list creation logic to custom logic later on
14865 				// TODO: Remove this when the list creation logic is removed
14866 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
14867 				if (listElm) {
14868 					listParent = listElm.parentNode;
14869 
14870 					// If list is within a text block then split that block
14871 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
14872 						storeSelection();
14873 						dom.split(listParent, listElm);
14874 						restoreSelection();
14875 					}
14876 				}
14877 			},
14878 
14879 			// Override commands to use the text formatter engine
14880 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
14881 				toggleFormat(command);
14882 			},
14883 
14884 			// Override commands to use the text formatter engine
14885 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
14886 				toggleFormat(command, value);
14887 			},
14888 
14889 			FontSize : function(command, ui, value) {
14890 				var fontClasses, fontSizes;
14891 
14892 				// Convert font size 1-7 to styles
14893 				if (value >= 1 && value <= 7) {
14894 					fontSizes = tinymce.explode(settings.font_size_style_values);
14895 					fontClasses = tinymce.explode(settings.font_size_classes);
14896 
14897 					if (fontClasses)
14898 						value = fontClasses[value - 1] || value;
14899 					else
14900 						value = fontSizes[value - 1] || value;
14901 				}
14902 
14903 				toggleFormat(command, value);
14904 			},
14905 
14906 			RemoveFormat : function(command) {
14907 				formatter.remove(command);
14908 			},
14909 
14910 			mceBlockQuote : function(command) {
14911 				toggleFormat('blockquote');
14912 			},
14913 
14914 			FormatBlock : function(command, ui, value) {
14915 				return toggleFormat(value || 'p');
14916 			},
14917 
14918 			mceCleanup : function() {
14919 				var bookmark = selection.getBookmark();
14920 
14921 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
14922 
14923 				selection.moveToBookmark(bookmark);
14924 			},
14925 
14926 			mceRemoveNode : function(command, ui, value) {
14927 				var node = value || selection.getNode();
14928 
14929 				// Make sure that the body node isn't removed
14930 				if (node != editor.getBody()) {
14931 					storeSelection();
14932 					editor.dom.remove(node, TRUE);
14933 					restoreSelection();
14934 				}
14935 			},
14936 
14937 			mceSelectNodeDepth : function(command, ui, value) {
14938 				var counter = 0;
14939 
14940 				dom.getParent(selection.getNode(), function(node) {
14941 					if (node.nodeType == 1 && counter++ == value) {
14942 						selection.select(node);
14943 						return FALSE;
14944 					}
14945 				}, editor.getBody());
14946 			},
14947 
14948 			mceSelectNode : function(command, ui, value) {
14949 				selection.select(value);
14950 			},
14951 
14952 			mceInsertContent : function(command, ui, value) {
14953 				var parser, serializer, parentNode, rootNode, fragment, args,
14954 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
14955 
14956 				//selection.normalize();
14957 
14958 				// Setup parser and serializer
14959 				parser = editor.parser;
14960 				serializer = new tinymce.html.Serializer({}, editor.schema);
14961 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
14962 
14963 				// Run beforeSetContent handlers on the HTML to be inserted
14964 				args = {content: value, format: 'html'};
14965 				selection.onBeforeSetContent.dispatch(selection, args);
14966 				value = args.content;
14967 
14968 				// Add caret at end of contents if it's missing
14969 				if (value.indexOf('{$caret}') == -1)
14970 					value += '{$caret}';
14971 
14972 				// Replace the caret marker with a span bookmark element
14973 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
14974 
14975 				// Insert node maker where we will insert the new HTML and get it's parent
14976 				if (!selection.isCollapsed())
14977 					editor.getDoc().execCommand('Delete', false, null);
14978 
14979 				parentNode = selection.getNode();
14980 
14981 				// Parse the fragment within the context of the parent node
14982 				args = {context : parentNode.nodeName.toLowerCase()};
14983 				fragment = parser.parse(value, args);
14984 
14985 				// Move the caret to a more suitable location
14986 				node = fragment.lastChild;
14987 				if (node.attr('id') == 'mce_marker') {
14988 					marker = node;
14989 
14990 					for (node = node.prev; node; node = node.walk(true)) {
14991 						if (node.type == 3 || !dom.isBlock(node.name)) {
14992 							node.parent.insert(marker, node, node.name === 'br');
14993 							break;
14994 						}
14995 					}
14996 				}
14997 
14998 				// If parser says valid we can insert the contents into that parent
14999 				if (!args.invalid) {
15000 					value = serializer.serialize(fragment);
15001 
15002 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
15003 					node = parentNode.firstChild;
15004 					node2 = parentNode.lastChild;
15005 					if (!node || (node === node2 && node.nodeName === 'BR'))
15006 						dom.setHTML(parentNode, value);
15007 					else
15008 						selection.setContent(value);
15009 				} else {
15010 					// If the fragment was invalid within that context then we need
15011 					// to parse and process the parent it's inserted into
15012 
15013 					// Insert bookmark node and get the parent
15014 					selection.setContent(bookmarkHtml);
15015 					parentNode = editor.selection.getNode();
15016 					rootNode = editor.getBody();
15017 
15018 					// Opera will return the document node when selection is in root
15019 					if (parentNode.nodeType == 9)
15020 						parentNode = node = rootNode;
15021 					else
15022 						node = parentNode;
15023 
15024 					// Find the ancestor just before the root element
15025 					while (node !== rootNode) {
15026 						parentNode = node;
15027 						node = node.parentNode;
15028 					}
15029 
15030 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15031 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15032 					value = serializer.serialize(
15033 						parser.parse(
15034 							// Need to replace by using a function since $ in the contents would otherwise be a problem
15035 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15036 								return serializer.serialize(fragment);
15037 							})
15038 						)
15039 					);
15040 
15041 					// Set the inner/outer HTML depending on if we are in the root or not
15042 					if (parentNode == rootNode)
15043 						dom.setHTML(rootNode, value);
15044 					else
15045 						dom.setOuterHTML(parentNode, value);
15046 				}
15047 
15048 				marker = dom.get('mce_marker');
15049 
15050 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15051 				nodeRect = dom.getRect(marker);
15052 				viewPortRect = dom.getViewPort(editor.getWin());
15053 
15054 				// Check if node is out side the viewport if it is then scroll to it
15055 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15056 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15057 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15058 					viewportBodyElement.scrollLeft = nodeRect.x;
15059 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15060 				}
15061 
15062 				// Move selection before marker and remove it
15063 				rng = dom.createRng();
15064 
15065 				// If previous sibling is a text node set the selection to the end of that node
15066 				node = marker.previousSibling;
15067 				if (node && node.nodeType == 3) {
15068 					rng.setStart(node, node.nodeValue.length);
15069 				} else {
15070 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15071 					rng.setStartBefore(marker);
15072 					rng.setEndBefore(marker);
15073 				}
15074 
15075 				// Remove the marker node and set the new range
15076 				dom.remove(marker);
15077 				selection.setRng(rng);
15078 
15079 				// Dispatch after event and add any visual elements needed
15080 				selection.onSetContent.dispatch(selection, args);
15081 				editor.addVisual();
15082 			},
15083 
15084 			mceInsertRawHTML : function(command, ui, value) {
15085 				selection.setContent('tiny_mce_marker');
15086 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15087 			},
15088 
15089 			mceToggleFormat : function(command, ui, value) {
15090 				toggleFormat(value);
15091 			},
15092 
15093 			mceSetContent : function(command, ui, value) {
15094 				editor.setContent(value);
15095 			},
15096 
15097 			'Indent,Outdent' : function(command) {
15098 				var intentValue, indentUnit, value;
15099 
15100 				// Setup indent level
15101 				intentValue = settings.indentation;
15102 				indentUnit = /[a-z%]+$/i.exec(intentValue);
15103 				intentValue = parseInt(intentValue);
15104 
15105 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15106 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15107 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15108 						formatter.apply('div');
15109 					}
15110 
15111 					each(selection.getSelectedBlocks(), function(element) {
15112 						if (command == 'outdent') {
15113 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15114 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15115 						} else
15116 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15117 					});
15118 				} else
15119 					execNativeCommand(command);
15120 			},
15121 
15122 			mceRepaint : function() {
15123 				var bookmark;
15124 
15125 				if (tinymce.isGecko) {
15126 					try {
15127 						storeSelection(TRUE);
15128 
15129 						if (selection.getSel())
15130 							selection.getSel().selectAllChildren(editor.getBody());
15131 
15132 						selection.collapse(TRUE);
15133 						restoreSelection();
15134 					} catch (ex) {
15135 						// Ignore
15136 					}
15137 				}
15138 			},
15139 
15140 			mceToggleFormat : function(command, ui, value) {
15141 				formatter.toggle(value);
15142 			},
15143 
15144 			InsertHorizontalRule : function() {
15145 				editor.execCommand('mceInsertContent', false, '<hr />');
15146 			},
15147 
15148 			mceToggleVisualAid : function() {
15149 				editor.hasVisual = !editor.hasVisual;
15150 				editor.addVisual();
15151 			},
15152 
15153 			mceReplaceContent : function(command, ui, value) {
15154 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15155 			},
15156 
15157 			mceInsertLink : function(command, ui, value) {
15158 				var anchor;
15159 
15160 				if (typeof(value) == 'string')
15161 					value = {href : value};
15162 
15163 				anchor = dom.getParent(selection.getNode(), 'a');
15164 
15165 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15166 				value.href = value.href.replace(' ', '%20');
15167 
15168 				// Remove existing links if there could be child links or that the href isn't specified
15169 				if (!anchor || !value.href) {
15170 					formatter.remove('link');
15171 				}		
15172 
15173 				// Apply new link to selection
15174 				if (value.href) {
15175 					formatter.apply('link', value, anchor);
15176 				}
15177 			},
15178 
15179 			selectAll : function() {
15180 				var root = dom.getRoot(), rng = dom.createRng();
15181 
15182 				rng.setStart(root, 0);
15183 				rng.setEnd(root, root.childNodes.length);
15184 
15185 				editor.selection.setRng(rng);
15186 			}
15187 		});
15188 
15189 		// Add queryCommandState overrides
15190 		addCommands({
15191 			// Override justify commands
15192 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15193 				var name = 'align' + command.substring(7);
15194 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15195 				var matches = tinymce.map(nodes, function(node) {
15196 					return !!formatter.matchNode(node, name);
15197 				});
15198 				return tinymce.inArray(matches, TRUE) !== -1;
15199 			},
15200 
15201 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15202 				return isFormatMatch(command);
15203 			},
15204 
15205 			mceBlockQuote : function() {
15206 				return isFormatMatch('blockquote');
15207 			},
15208 
15209 			Outdent : function() {
15210 				var node;
15211 
15212 				if (settings.inline_styles) {
15213 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15214 						return TRUE;
15215 
15216 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15217 						return TRUE;
15218 				}
15219 
15220 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15221 			},
15222 
15223 			'InsertUnorderedList,InsertOrderedList' : function(command) {
15224 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
15225 			}
15226 		}, 'state');
15227 
15228 		// Add queryCommandValue overrides
15229 		addCommands({
15230 			'FontSize,FontName' : function(command) {
15231 				var value = 0, parent;
15232 
15233 				if (parent = dom.getParent(selection.getNode(), 'span')) {
15234 					if (command == 'fontsize')
15235 						value = parent.style.fontSize;
15236 					else
15237 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15238 				}
15239 
15240 				return value;
15241 			}
15242 		}, 'value');
15243 
15244 		// Add undo manager logic
15245 		addCommands({
15246 			Undo : function() {
15247 				editor.undoManager.undo();
15248 			},
15249 
15250 			Redo : function() {
15251 				editor.undoManager.redo();
15252 			}
15253 		});
15254 	};
15255 })(tinymce);
15256 
15257 (function(tinymce) {
15258 	var Dispatcher = tinymce.util.Dispatcher;
15259 
15260 	tinymce.UndoManager = function(editor) {
15261 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15262 
15263 		function getContent() {
15264 			// Remove whitespace before/after and remove pure bogus nodes
15265 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15266 		};
15267 
15268 		function addNonTypingUndoLevel() {
15269 			self.typing = false;
15270 			self.add();
15271 		};
15272 
15273 		// Create event instances
15274 		onBeforeAdd = new Dispatcher(self);
15275 		onAdd       = new Dispatcher(self);
15276 		onUndo      = new Dispatcher(self);
15277 		onRedo      = new Dispatcher(self);
15278 
15279 		// Pass though onAdd event from UndoManager to Editor as onChange
15280 		onAdd.add(function(undoman, level) {
15281 			if (undoman.hasUndo())
15282 				return editor.onChange.dispatch(editor, level, undoman);
15283 		});
15284 
15285 		// Pass though onUndo event from UndoManager to Editor
15286 		onUndo.add(function(undoman, level) {
15287 			return editor.onUndo.dispatch(editor, level, undoman);
15288 		});
15289 
15290 		// Pass though onRedo event from UndoManager to Editor
15291 		onRedo.add(function(undoman, level) {
15292 			return editor.onRedo.dispatch(editor, level, undoman);
15293 		});
15294 
15295 		// Add initial undo level when the editor is initialized
15296 		editor.onInit.add(function() {
15297 			self.add();
15298 		});
15299 
15300 		// Get position before an execCommand is processed
15301 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15302 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15303 				self.beforeChange();
15304 			}
15305 		});
15306 
15307 		// Add undo level after an execCommand call was made
15308 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15309 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15310 				self.add();
15311 			}
15312 		});
15313 
15314 		// Add undo level on save contents, drag end and blur/focusout
15315 		editor.onSaveContent.add(addNonTypingUndoLevel);
15316 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15317 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
15318 			if (!editor.removed && self.typing) {
15319 				addNonTypingUndoLevel();
15320 			}
15321 		});
15322 
15323 		editor.onKeyUp.add(function(editor, e) {
15324 			var keyCode = e.keyCode;
15325 
15326 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15327 				addNonTypingUndoLevel();
15328 			}
15329 		});
15330 
15331 		editor.onKeyDown.add(function(editor, e) {
15332 			var keyCode = e.keyCode;
15333 
15334 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15335 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15336 				if (self.typing) {
15337 					addNonTypingUndoLevel();
15338 				}
15339 
15340 				return;
15341 			}
15342 
15343 			// If key isn't shift,ctrl,alt,capslock,metakey
15344 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15345 				self.beforeChange();
15346 				self.typing = true;
15347 				self.add();
15348 			}
15349 		});
15350 
15351 		editor.onMouseDown.add(function(editor, e) {
15352 			if (self.typing) {
15353 				addNonTypingUndoLevel();
15354 			}
15355 		});
15356 
15357 		// Add keyboard shortcuts for undo/redo keys
15358 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15359 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15360 
15361 		self = {
15362 			// Explose for debugging reasons
15363 			data : data,
15364 
15365 			typing : false,
15366 			
15367 			onBeforeAdd: onBeforeAdd,
15368 
15369 			onAdd : onAdd,
15370 
15371 			onUndo : onUndo,
15372 
15373 			onRedo : onRedo,
15374 
15375 			beforeChange : function() {
15376 				beforeBookmark = editor.selection.getBookmark(2, true);
15377 			},
15378 
15379 			add : function(level) {
15380 				var i, settings = editor.settings, lastLevel;
15381 
15382 				level = level || {};
15383 				level.content = getContent();
15384 				
15385 				self.onBeforeAdd.dispatch(self, level);
15386 
15387 				// Add undo level if needed
15388 				lastLevel = data[index];
15389 				if (lastLevel && lastLevel.content == level.content)
15390 					return null;
15391 
15392 				// Set before bookmark on previous level
15393 				if (data[index])
15394 					data[index].beforeBookmark = beforeBookmark;
15395 
15396 				// Time to compress
15397 				if (settings.custom_undo_redo_levels) {
15398 					if (data.length > settings.custom_undo_redo_levels) {
15399 						for (i = 0; i < data.length - 1; i++)
15400 							data[i] = data[i + 1];
15401 
15402 						data.length--;
15403 						index = data.length;
15404 					}
15405 				}
15406 
15407 				// Get a non intrusive normalized bookmark
15408 				level.bookmark = editor.selection.getBookmark(2, true);
15409 
15410 				// Crop array if needed
15411 				if (index < data.length - 1)
15412 					data.length = index + 1;
15413 
15414 				data.push(level);
15415 				index = data.length - 1;
15416 
15417 				self.onAdd.dispatch(self, level);
15418 				editor.isNotDirty = 0;
15419 
15420 				return level;
15421 			},
15422 
15423 			undo : function() {
15424 				var level, i;
15425 
15426 				if (self.typing) {
15427 					self.add();
15428 					self.typing = false;
15429 				}
15430 
15431 				if (index > 0) {
15432 					level = data[--index];
15433 
15434 					editor.setContent(level.content, {format : 'raw'});
15435 					editor.selection.moveToBookmark(level.beforeBookmark);
15436 
15437 					self.onUndo.dispatch(self, level);
15438 				}
15439 
15440 				return level;
15441 			},
15442 
15443 			redo : function() {
15444 				var level;
15445 
15446 				if (index < data.length - 1) {
15447 					level = data[++index];
15448 
15449 					editor.setContent(level.content, {format : 'raw'});
15450 					editor.selection.moveToBookmark(level.bookmark);
15451 
15452 					self.onRedo.dispatch(self, level);
15453 				}
15454 
15455 				return level;
15456 			},
15457 
15458 			clear : function() {
15459 				data = [];
15460 				index = 0;
15461 				self.typing = false;
15462 			},
15463 
15464 			hasUndo : function() {
15465 				return index > 0 || this.typing;
15466 			},
15467 
15468 			hasRedo : function() {
15469 				return index < data.length - 1 && !this.typing;
15470 			}
15471 		};
15472 
15473 		return self;
15474 	};
15475 })(tinymce);
15476 
15477 tinymce.ForceBlocks = function(editor) {
15478 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15479 
15480 	function addRootBlocks() {
15481 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15482 
15483 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15484 			return;
15485 
15486 		// Check if node is wrapped in block
15487 		while (node && node != rootNode) {
15488 			if (blockElements[node.nodeName])
15489 				return;
15490 
15491 			node = node.parentNode;
15492 		}
15493 
15494 		// Get current selection
15495 		rng = selection.getRng();
15496 		if (rng.setStart) {
15497 			startContainer = rng.startContainer;
15498 			startOffset = rng.startOffset;
15499 			endContainer = rng.endContainer;
15500 			endOffset = rng.endOffset;
15501 		} else {
15502 			// Force control range into text range
15503 			if (rng.item) {
15504 				node = rng.item(0);
15505 				rng = editor.getDoc().body.createTextRange();
15506 				rng.moveToElementText(node);
15507 			}
15508 
15509 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15510 			tmpRng = rng.duplicate();
15511 			tmpRng.collapse(true);
15512 			startOffset = tmpRng.move('character', offset) * -1;
15513 
15514 			if (!tmpRng.collapsed) {
15515 				tmpRng = rng.duplicate();
15516 				tmpRng.collapse(false);
15517 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15518 			}
15519 		}
15520 
15521 		// Wrap non block elements and text nodes
15522 		node = rootNode.firstChild;
15523 		while (node) {
15524 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15525 				if (!rootBlockNode) {
15526 					rootBlockNode = dom.create(settings.forced_root_block);
15527 					node.parentNode.insertBefore(rootBlockNode, node);
15528 					wrapped = true;
15529 				}
15530 
15531 				tempNode = node;
15532 				node = node.nextSibling;
15533 				rootBlockNode.appendChild(tempNode);
15534 			} else {
15535 				rootBlockNode = null;
15536 				node = node.nextSibling;
15537 			}
15538 		}
15539 
15540 		if (wrapped) {
15541 			if (rng.setStart) {
15542 				rng.setStart(startContainer, startOffset);
15543 				rng.setEnd(endContainer, endOffset);
15544 				selection.setRng(rng);
15545 			} else {
15546 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15547 				if (isInEditorDocument) {
15548 					try {
15549 						rng = editor.getDoc().body.createTextRange();
15550 						rng.moveToElementText(rootNode);
15551 						rng.collapse(true);
15552 						rng.moveStart('character', startOffset);
15553 
15554 						if (endOffset > 0)
15555 							rng.moveEnd('character', endOffset);
15556 
15557 						rng.select();
15558 					} catch (ex) {
15559 						// Ignore
15560 					}
15561 				}
15562 			}
15563 
15564 			editor.nodeChanged();
15565 		}
15566 	};
15567 
15568 	// Force root blocks
15569 	if (settings.forced_root_block) {
15570 		editor.onKeyUp.add(addRootBlocks);
15571 		editor.onNodeChange.add(addRootBlocks);
15572 	}
15573 };
15574 
15575 (function(tinymce) {
15576 	// Shorten names
15577 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15578 
15579 	tinymce.create('tinymce.ControlManager', {
15580 		ControlManager : function(ed, s) {
15581 			var t = this, i;
15582 
15583 			s = s || {};
15584 			t.editor = ed;
15585 			t.controls = {};
15586 			t.onAdd = new tinymce.util.Dispatcher(t);
15587 			t.onPostRender = new tinymce.util.Dispatcher(t);
15588 			t.prefix = s.prefix || ed.id + '_';
15589 			t._cls = {};
15590 
15591 			t.onPostRender.add(function() {
15592 				each(t.controls, function(c) {
15593 					c.postRender();
15594 				});
15595 			});
15596 		},
15597 
15598 		get : function(id) {
15599 			return this.controls[this.prefix + id] || this.controls[id];
15600 		},
15601 
15602 		setActive : function(id, s) {
15603 			var c = null;
15604 
15605 			if (c = this.get(id))
15606 				c.setActive(s);
15607 
15608 			return c;
15609 		},
15610 
15611 		setDisabled : function(id, s) {
15612 			var c = null;
15613 
15614 			if (c = this.get(id))
15615 				c.setDisabled(s);
15616 
15617 			return c;
15618 		},
15619 
15620 		add : function(c) {
15621 			var t = this;
15622 
15623 			if (c) {
15624 				t.controls[c.id] = c;
15625 				t.onAdd.dispatch(c, t);
15626 			}
15627 
15628 			return c;
15629 		},
15630 
15631 		createControl : function(name) {
15632 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15633 
15634 			// Build control factory cache
15635 			if (!self.controlFactories) {
15636 				self.controlFactories = [];
15637 				each(editor.plugins, function(plugin) {
15638 					if (plugin.createControl) {
15639 						self.controlFactories.push(plugin);
15640 					}
15641 				});
15642 			}
15643 
15644 			// Create controls by asking cached factories
15645 			factories = self.controlFactories;
15646 			for (i = 0, l = factories.length; i < l; i++) {
15647 				ctrl = factories[i].createControl(name, self);
15648 
15649 				if (ctrl) {
15650 					return self.add(ctrl);
15651 				}
15652 			}
15653 
15654 			// Create sepearator
15655 			if (name === "|" || name === "separator") {
15656 				return self.createSeparator();
15657 			}
15658 
15659 			// Create control from button collection
15660 			if (editor.buttons && (ctrl = editor.buttons[name])) {
15661 				return self.createButton(name, ctrl);
15662 			}
15663 
15664 			return self.add(ctrl);
15665 		},
15666 
15667 		createDropMenu : function(id, s, cc) {
15668 			var t = this, ed = t.editor, c, bm, v, cls;
15669 
15670 			s = extend({
15671 				'class' : 'mceDropDown',
15672 				constrain : ed.settings.constrain_menus
15673 			}, s);
15674 
15675 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
15676 			if (v = ed.getParam('skin_variant'))
15677 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
15678 
15679 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
15680 
15681 			id = t.prefix + id;
15682 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
15683 			c = t.controls[id] = new cls(id, s);
15684 			c.onAddItem.add(function(c, o) {
15685 				var s = o.settings;
15686 
15687 				s.title = ed.getLang(s.title, s.title);
15688 
15689 				if (!s.onclick) {
15690 					s.onclick = function(v) {
15691 						if (s.cmd)
15692 							ed.execCommand(s.cmd, s.ui || false, s.value);
15693 					};
15694 				}
15695 			});
15696 
15697 			ed.onRemove.add(function() {
15698 				c.destroy();
15699 			});
15700 
15701 			// Fix for bug #1897785, #1898007
15702 			if (tinymce.isIE) {
15703 				c.onShowMenu.add(function() {
15704 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15705 					ed.focus();
15706 
15707 					bm = ed.selection.getBookmark(1);
15708 				});
15709 
15710 				c.onHideMenu.add(function() {
15711 					if (bm) {
15712 						ed.selection.moveToBookmark(bm);
15713 						bm = 0;
15714 					}
15715 				});
15716 			}
15717 
15718 			return t.add(c);
15719 		},
15720 
15721 		createListBox : function(id, s, cc) {
15722 			var t = this, ed = t.editor, cmd, c, cls;
15723 
15724 			if (t.get(id))
15725 				return null;
15726 
15727 			s.title = ed.translate(s.title);
15728 			s.scope = s.scope || ed;
15729 
15730 			if (!s.onselect) {
15731 				s.onselect = function(v) {
15732 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15733 				};
15734 			}
15735 
15736 			s = extend({
15737 				title : s.title,
15738 				'class' : 'mce_' + id,
15739 				scope : s.scope,
15740 				control_manager : t
15741 			}, s);
15742 
15743 			id = t.prefix + id;
15744 
15745 
15746 			function useNativeListForAccessibility(ed) {
15747 				return ed.settings.use_accessible_selects && !tinymce.isGecko
15748 			}
15749 
15750 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
15751 				c = new tinymce.ui.NativeListBox(id, s);
15752 			else {
15753 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
15754 				c = new cls(id, s, ed);
15755 			}
15756 
15757 			t.controls[id] = c;
15758 
15759 			// Fix focus problem in Safari
15760 			if (tinymce.isWebKit) {
15761 				c.onPostRender.add(function(c, n) {
15762 					// Store bookmark on mousedown
15763 					Event.add(n, 'mousedown', function() {
15764 						ed.bookmark = ed.selection.getBookmark(1);
15765 					});
15766 
15767 					// Restore on focus, since it might be lost
15768 					Event.add(n, 'focus', function() {
15769 						ed.selection.moveToBookmark(ed.bookmark);
15770 						ed.bookmark = null;
15771 					});
15772 				});
15773 			}
15774 
15775 			if (c.hideMenu)
15776 				ed.onMouseDown.add(c.hideMenu, c);
15777 
15778 			return t.add(c);
15779 		},
15780 
15781 		createButton : function(id, s, cc) {
15782 			var t = this, ed = t.editor, o, c, cls;
15783 
15784 			if (t.get(id))
15785 				return null;
15786 
15787 			s.title = ed.translate(s.title);
15788 			s.label = ed.translate(s.label);
15789 			s.scope = s.scope || ed;
15790 
15791 			if (!s.onclick && !s.menu_button) {
15792 				s.onclick = function() {
15793 					ed.execCommand(s.cmd, s.ui || false, s.value);
15794 				};
15795 			}
15796 
15797 			s = extend({
15798 				title : s.title,
15799 				'class' : 'mce_' + id,
15800 				unavailable_prefix : ed.getLang('unavailable', ''),
15801 				scope : s.scope,
15802 				control_manager : t
15803 			}, s);
15804 
15805 			id = t.prefix + id;
15806 
15807 			if (s.menu_button) {
15808 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
15809 				c = new cls(id, s, ed);
15810 				ed.onMouseDown.add(c.hideMenu, c);
15811 			} else {
15812 				cls = t._cls.button || tinymce.ui.Button;
15813 				c = new cls(id, s, ed);
15814 			}
15815 
15816 			return t.add(c);
15817 		},
15818 
15819 		createMenuButton : function(id, s, cc) {
15820 			s = s || {};
15821 			s.menu_button = 1;
15822 
15823 			return this.createButton(id, s, cc);
15824 		},
15825 
15826 		createSplitButton : function(id, s, cc) {
15827 			var t = this, ed = t.editor, cmd, c, cls;
15828 
15829 			if (t.get(id))
15830 				return null;
15831 
15832 			s.title = ed.translate(s.title);
15833 			s.scope = s.scope || ed;
15834 
15835 			if (!s.onclick) {
15836 				s.onclick = function(v) {
15837 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15838 				};
15839 			}
15840 
15841 			if (!s.onselect) {
15842 				s.onselect = function(v) {
15843 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15844 				};
15845 			}
15846 
15847 			s = extend({
15848 				title : s.title,
15849 				'class' : 'mce_' + id,
15850 				scope : s.scope,
15851 				control_manager : t
15852 			}, s);
15853 
15854 			id = t.prefix + id;
15855 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
15856 			c = t.add(new cls(id, s, ed));
15857 			ed.onMouseDown.add(c.hideMenu, c);
15858 
15859 			return c;
15860 		},
15861 
15862 		createColorSplitButton : function(id, s, cc) {
15863 			var t = this, ed = t.editor, cmd, c, cls, bm;
15864 
15865 			if (t.get(id))
15866 				return null;
15867 
15868 			s.title = ed.translate(s.title);
15869 			s.scope = s.scope || ed;
15870 
15871 			if (!s.onclick) {
15872 				s.onclick = function(v) {
15873 					if (tinymce.isIE)
15874 						bm = ed.selection.getBookmark(1);
15875 
15876 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15877 				};
15878 			}
15879 
15880 			if (!s.onselect) {
15881 				s.onselect = function(v) {
15882 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
15883 				};
15884 			}
15885 
15886 			s = extend({
15887 				title : s.title,
15888 				'class' : 'mce_' + id,
15889 				'menu_class' : ed.getParam('skin') + 'Skin',
15890 				scope : s.scope,
15891 				more_colors_title : ed.getLang('more_colors')
15892 			}, s);
15893 
15894 			id = t.prefix + id;
15895 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
15896 			c = new cls(id, s, ed);
15897 			ed.onMouseDown.add(c.hideMenu, c);
15898 
15899 			// Remove the menu element when the editor is removed
15900 			ed.onRemove.add(function() {
15901 				c.destroy();
15902 			});
15903 
15904 			// Fix for bug #1897785, #1898007
15905 			if (tinymce.isIE) {
15906 				c.onShowMenu.add(function() {
15907 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
15908 					ed.focus();
15909 					bm = ed.selection.getBookmark(1);
15910 				});
15911 
15912 				c.onHideMenu.add(function() {
15913 					if (bm) {
15914 						ed.selection.moveToBookmark(bm);
15915 						bm = 0;
15916 					}
15917 				});
15918 			}
15919 
15920 			return t.add(c);
15921 		},
15922 
15923 		createToolbar : function(id, s, cc) {
15924 			var c, t = this, cls;
15925 
15926 			id = t.prefix + id;
15927 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
15928 			c = new cls(id, s, t.editor);
15929 
15930 			if (t.get(id))
15931 				return null;
15932 
15933 			return t.add(c);
15934 		},
15935 		
15936 		createToolbarGroup : function(id, s, cc) {
15937 			var c, t = this, cls;
15938 			id = t.prefix + id;
15939 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
15940 			c = new cls(id, s, t.editor);
15941 			
15942 			if (t.get(id))
15943 				return null;
15944 			
15945 			return t.add(c);
15946 		},
15947 
15948 		createSeparator : function(cc) {
15949 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
15950 
15951 			return new cls();
15952 		},
15953 
15954 		setControlType : function(n, c) {
15955 			return this._cls[n.toLowerCase()] = c;
15956 		},
15957 	
15958 		destroy : function() {
15959 			each(this.controls, function(c) {
15960 				c.destroy();
15961 			});
15962 
15963 			this.controls = null;
15964 		}
15965 	});
15966 })(tinymce);
15967 
15968 (function(tinymce) {
15969 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
15970 
15971 	tinymce.create('tinymce.WindowManager', {
15972 		WindowManager : function(ed) {
15973 			var t = this;
15974 
15975 			t.editor = ed;
15976 			t.onOpen = new Dispatcher(t);
15977 			t.onClose = new Dispatcher(t);
15978 			t.params = {};
15979 			t.features = {};
15980 		},
15981 
15982 		open : function(s, p) {
15983 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
15984 
15985 			// Default some options
15986 			s = s || {};
15987 			p = p || {};
15988 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
15989 			sh = isOpera ? vp.h : screen.height;
15990 			s.name = s.name || 'mc_' + new Date().getTime();
15991 			s.width = parseInt(s.width || 320);
15992 			s.height = parseInt(s.height || 240);
15993 			s.resizable = true;
15994 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
15995 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
15996 			p.inline = false;
15997 			p.mce_width = s.width;
15998 			p.mce_height = s.height;
15999 			p.mce_auto_focus = s.auto_focus;
16000 
16001 			if (mo) {
16002 				if (isIE) {
16003 					s.center = true;
16004 					s.help = false;
16005 					s.dialogWidth = s.width + 'px';
16006 					s.dialogHeight = s.height + 'px';
16007 					s.scroll = s.scrollbars || false;
16008 				}
16009 			}
16010 
16011 			// Build features string
16012 			each(s, function(v, k) {
16013 				if (tinymce.is(v, 'boolean'))
16014 					v = v ? 'yes' : 'no';
16015 
16016 				if (!/^(name|url)$/.test(k)) {
16017 					if (isIE && mo)
16018 						f += (f ? ';' : '') + k + ':' + v;
16019 					else
16020 						f += (f ? ',' : '') + k + '=' + v;
16021 				}
16022 			});
16023 
16024 			t.features = s;
16025 			t.params = p;
16026 			t.onOpen.dispatch(t, s, p);
16027 
16028 			u = s.url || s.file;
16029 			u = tinymce._addVer(u);
16030 
16031 			try {
16032 				if (isIE && mo) {
16033 					w = 1;
16034 					window.showModalDialog(u, window, f);
16035 				} else
16036 					w = window.open(u, s.name, f);
16037 			} catch (ex) {
16038 				// Ignore
16039 			}
16040 
16041 			if (!w)
16042 				alert(t.editor.getLang('popup_blocked'));
16043 		},
16044 
16045 		close : function(w) {
16046 			w.close();
16047 			this.onClose.dispatch(this);
16048 		},
16049 
16050 		createInstance : function(cl, a, b, c, d, e) {
16051 			var f = tinymce.resolve(cl);
16052 
16053 			return new f(a, b, c, d, e);
16054 		},
16055 
16056 		confirm : function(t, cb, s, w) {
16057 			w = w || window;
16058 
16059 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16060 		},
16061 
16062 		alert : function(tx, cb, s, w) {
16063 			var t = this;
16064 
16065 			w = w || window;
16066 			w.alert(t._decode(t.editor.getLang(tx, tx)));
16067 
16068 			if (cb)
16069 				cb.call(s || t);
16070 		},
16071 
16072 		resizeBy : function(dw, dh, win) {
16073 			win.resizeBy(dw, dh);
16074 		},
16075 
16076 		// Internal functions
16077 
16078 		_decode : function(s) {
16079 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16080 		}
16081 	});
16082 }(tinymce));
16083 (function(tinymce) {
16084 	tinymce.Formatter = function(ed) {
16085 		var formats = {},
16086 			each = tinymce.each,
16087 			dom = ed.dom,
16088 			selection = ed.selection,
16089 			TreeWalker = tinymce.dom.TreeWalker,
16090 			rangeUtils = new tinymce.dom.RangeUtils(dom),
16091 			isValid = ed.schema.isValidChild,
16092 			isBlock = dom.isBlock,
16093 			forcedRootBlock = ed.settings.forced_root_block,
16094 			nodeIndex = dom.nodeIndex,
16095 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
16096 			MCE_ATTR_RE = /^(src|href|style)$/,
16097 			FALSE = false,
16098 			TRUE = true,
16099 			formatChangeData,
16100 			undef,
16101 			getContentEditable = dom.getContentEditable;
16102 
16103 		function isArray(obj) {
16104 			return obj instanceof Array;
16105 		};
16106 
16107 		function getParents(node, selector) {
16108 			return dom.getParents(node, selector, dom.getRoot());
16109 		};
16110 
16111 		function isCaretNode(node) {
16112 			return node.nodeType === 1 && node.id === '_mce_caret';
16113 		};
16114 
16115 		function defaultFormats() {
16116 			register({
16117 				alignleft : [
16118 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16119 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16120 				],
16121 
16122 				aligncenter : [
16123 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16124 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16125 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16126 				],
16127 
16128 				alignright : [
16129 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16130 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16131 				],
16132 
16133 				alignfull : [
16134 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16135 				],
16136 
16137 				bold : [
16138 					{inline : 'strong', remove : 'all'},
16139 					{inline : 'span', styles : {fontWeight : 'bold'}},
16140 					{inline : 'b', remove : 'all'}
16141 				],
16142 
16143 				italic : [
16144 					{inline : 'em', remove : 'all'},
16145 					{inline : 'span', styles : {fontStyle : 'italic'}},
16146 					{inline : 'i', remove : 'all'}
16147 				],
16148 
16149 				underline : [
16150 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16151 					{inline : 'u', remove : 'all'}
16152 				],
16153 
16154 				strikethrough : [
16155 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16156 					{inline : 'strike', remove : 'all'}
16157 				],
16158 
16159 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16160 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16161 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16162 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16163 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16164 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16165 				subscript : {inline : 'sub'},
16166 				superscript : {inline : 'sup'},
16167 
16168 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16169 					onmatch : function(node) {
16170 						return true;
16171 					},
16172 
16173 					onformat : function(elm, fmt, vars) {
16174 						each(vars, function(value, key) {
16175 							dom.setAttrib(elm, key, value);
16176 						});
16177 					}
16178 				},
16179 
16180 				removeformat : [
16181 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16182 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16183 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16184 				]
16185 			});
16186 
16187 			// Register default block formats
16188 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16189 				register(name, {block : name, remove : 'all'});
16190 			});
16191 
16192 			// Register user defined formats
16193 			register(ed.settings.formats);
16194 		};
16195 
16196 		function addKeyboardShortcuts() {
16197 			// Add some inline shortcuts
16198 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16199 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16200 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16201 
16202 			// BlockFormat shortcuts keys
16203 			for (var i = 1; i <= 6; i++) {
16204 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16205 			}
16206 
16207 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16208 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16209 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16210 		};
16211 
16212 		// Public functions
16213 
16214 		function get(name) {
16215 			return name ? formats[name] : formats;
16216 		};
16217 
16218 		function register(name, format) {
16219 			if (name) {
16220 				if (typeof(name) !== 'string') {
16221 					each(name, function(format, name) {
16222 						register(name, format);
16223 					});
16224 				} else {
16225 					// Force format into array and add it to internal collection
16226 					format = format.length ? format : [format];
16227 
16228 					each(format, function(format) {
16229 						// Set deep to false by default on selector formats this to avoid removing
16230 						// alignment on images inside paragraphs when alignment is changed on paragraphs
16231 						if (format.deep === undef)
16232 							format.deep = !format.selector;
16233 
16234 						// Default to true
16235 						if (format.split === undef)
16236 							format.split = !format.selector || format.inline;
16237 
16238 						// Default to true
16239 						if (format.remove === undef && format.selector && !format.inline)
16240 							format.remove = 'none';
16241 
16242 						// Mark format as a mixed format inline + block level
16243 						if (format.selector && format.inline) {
16244 							format.mixed = true;
16245 							format.block_expand = true;
16246 						}
16247 
16248 						// Split classes if needed
16249 						if (typeof(format.classes) === 'string')
16250 							format.classes = format.classes.split(/\s+/);
16251 					});
16252 
16253 					formats[name] = format;
16254 				}
16255 			}
16256 		};
16257 
16258 		var getTextDecoration = function(node) {
16259 			var decoration;
16260 
16261 			ed.dom.getParent(node, function(n) {
16262 				decoration = ed.dom.getStyle(n, 'text-decoration');
16263 				return decoration && decoration !== 'none';
16264 			});
16265 
16266 			return decoration;
16267 		};
16268 
16269 		var processUnderlineAndColor = function(node) {
16270 			var textDecoration;
16271 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16272 				textDecoration = getTextDecoration(node.parentNode);
16273 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
16274 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
16275 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16276 					ed.dom.setStyle(node, 'text-decoration', null);
16277 				}
16278 			}
16279 		};
16280 
16281 		function apply(name, vars, node) {
16282 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16283 
16284 			function setElementFormat(elm, fmt) {
16285 				fmt = fmt || format;
16286 
16287 				if (elm) {
16288 					if (fmt.onformat) {
16289 						fmt.onformat(elm, fmt, vars, node);
16290 					}
16291 
16292 					each(fmt.styles, function(value, name) {
16293 						dom.setStyle(elm, name, replaceVars(value, vars));
16294 					});
16295 
16296 					each(fmt.attributes, function(value, name) {
16297 						dom.setAttrib(elm, name, replaceVars(value, vars));
16298 					});
16299 
16300 					each(fmt.classes, function(value) {
16301 						value = replaceVars(value, vars);
16302 
16303 						if (!dom.hasClass(elm, value))
16304 							dom.addClass(elm, value);
16305 					});
16306 				}
16307 			};
16308 			function adjustSelectionToVisibleSelection() {
16309 				function findSelectionEnd(start, end) {
16310 					var walker = new TreeWalker(end);
16311 					for (node = walker.current(); node; node = walker.prev()) {
16312 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16313 							return node;
16314 						}
16315 					}
16316 				};
16317 
16318 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
16319 				// as this isn't visible to the user.
16320 				var rng = ed.selection.getRng();
16321 				var start = rng.startContainer;
16322 				var end = rng.endContainer;
16323 
16324 				if (start != end && rng.endOffset === 0) {
16325 					var newEnd = findSelectionEnd(start, end);
16326 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16327 
16328 					rng.setEnd(newEnd, endOffset);
16329 				}
16330 
16331 				return rng;
16332 			}
16333 			
16334 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16335 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16336 				
16337 				// find the index of the first child list.
16338 				each(node.childNodes, function(n, index) {
16339 					if (n.nodeName === "UL" || n.nodeName === "OL") {
16340 						listIndex = index;
16341 						list = n;
16342 						return false;
16343 					}
16344 				});
16345 				
16346 				// get the index of the bookmarks
16347 				each(node.childNodes, function(n, index) {
16348 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16349 						if (n.id == bookmark.id + "_start") {
16350 							startIndex = index;
16351 						} else if (n.id == bookmark.id + "_end") {
16352 							endIndex = index;
16353 						}
16354 					}
16355 				});
16356 				
16357 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16358 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16359 					each(tinymce.grep(node.childNodes), process);
16360 					return 0;
16361 				} else {
16362 					currentWrapElm = dom.clone(wrapElm, FALSE);
16363 
16364 					// create a list of the nodes on the same side of the list as the selection
16365 					each(tinymce.grep(node.childNodes), function(n, index) {
16366 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16367 							nodes.push(n); 
16368 							n.parentNode.removeChild(n);
16369 						}
16370 					});
16371 
16372 					// insert the wrapping element either before or after the list.
16373 					if (startIndex < listIndex) {
16374 						node.insertBefore(currentWrapElm, list);
16375 					} else if (startIndex > listIndex) {
16376 						node.insertBefore(currentWrapElm, list.nextSibling);
16377 					}
16378 					
16379 					// add the new nodes to the list.
16380 					newWrappers.push(currentWrapElm);
16381 
16382 					each(nodes, function(node) {
16383 						currentWrapElm.appendChild(node);
16384 					});
16385 
16386 					return currentWrapElm;
16387 				}
16388 			};
16389 
16390 			function applyRngStyle(rng, bookmark, node_specific) {
16391 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16392 
16393 				// Setup wrapper element
16394 				wrapName = format.inline || format.block;
16395 				wrapElm = dom.create(wrapName);
16396 				setElementFormat(wrapElm);
16397 
16398 				rangeUtils.walk(rng, function(nodes) {
16399 					var currentWrapElm;
16400 
16401 					function process(node) {
16402 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16403 
16404 						lastContentEditable = contentEditable;
16405 						nodeName = node.nodeName.toLowerCase();
16406 						parentName = node.parentNode.nodeName.toLowerCase();
16407 
16408 						// Node has a contentEditable value
16409 						if (node.nodeType === 1 && getContentEditable(node)) {
16410 							lastContentEditable = contentEditable;
16411 							contentEditable = getContentEditable(node) === "true";
16412 							hasContentEditableState = true; // We don't want to wrap the container only it's children
16413 						}
16414 
16415 						// Stop wrapping on br elements
16416 						if (isEq(nodeName, 'br')) {
16417 							currentWrapElm = 0;
16418 
16419 							// Remove any br elements when we wrap things
16420 							if (format.block)
16421 								dom.remove(node);
16422 
16423 							return;
16424 						}
16425 
16426 						// If node is wrapper type
16427 						if (format.wrapper && matchNode(node, name, vars)) {
16428 							currentWrapElm = 0;
16429 							return;
16430 						}
16431 
16432 						// Can we rename the block
16433 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16434 							node = dom.rename(node, wrapName);
16435 							setElementFormat(node);
16436 							newWrappers.push(node);
16437 							currentWrapElm = 0;
16438 							return;
16439 						}
16440 
16441 						// Handle selector patterns
16442 						if (format.selector) {
16443 							// Look for matching formats
16444 							each(formatList, function(format) {
16445 								// Check collapsed state if it exists
16446 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
16447 									return;
16448 								}
16449 
16450 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
16451 									setElementFormat(node, format);
16452 									found = true;
16453 								}
16454 							});
16455 
16456 							// Continue processing if a selector match wasn't found and a inline element is defined
16457 							if (!format.inline || found) {
16458 								currentWrapElm = 0;
16459 								return;
16460 							}
16461 						}
16462 
16463 						// Is it valid to wrap this item
16464 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16465 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
16466 							// Start wrapping
16467 							if (!currentWrapElm) {
16468 								// Wrap the node
16469 								currentWrapElm = dom.clone(wrapElm, FALSE);
16470 								node.parentNode.insertBefore(currentWrapElm, node);
16471 								newWrappers.push(currentWrapElm);
16472 							}
16473 
16474 							currentWrapElm.appendChild(node);
16475 						} else if (nodeName == 'li' && bookmark) {
16476 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16477 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16478 						} else {
16479 							// Start a new wrapper for possible children
16480 							currentWrapElm = 0;
16481 							
16482 							each(tinymce.grep(node.childNodes), process);
16483 
16484 							if (hasContentEditableState) {
16485 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16486 							}
16487 
16488 							// End the last wrapper
16489 							currentWrapElm = 0;
16490 						}
16491 					};
16492 
16493 					// Process siblings from range
16494 					each(nodes, process);
16495 				});
16496 
16497 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
16498 				if (format.wrap_links === false) {
16499 					each(newWrappers, function(node) {
16500 						function process(node) {
16501 							var i, currentWrapElm, children;
16502 
16503 							if (node.nodeName === 'A') {
16504 								currentWrapElm = dom.clone(wrapElm, FALSE);
16505 								newWrappers.push(currentWrapElm);
16506 
16507 								children = tinymce.grep(node.childNodes);
16508 								for (i = 0; i < children.length; i++)
16509 									currentWrapElm.appendChild(children[i]);
16510 
16511 								node.appendChild(currentWrapElm);
16512 							}
16513 
16514 							each(tinymce.grep(node.childNodes), process);
16515 						};
16516 
16517 						process(node);
16518 					});
16519 				}
16520 
16521 				// Cleanup
16522 				
16523 				each(newWrappers, function(node) {
16524 					var childCount;
16525 
16526 					function getChildCount(node) {
16527 						var count = 0;
16528 
16529 						each(node.childNodes, function(node) {
16530 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16531 								count++;
16532 						});
16533 
16534 						return count;
16535 					};
16536 
16537 					function mergeStyles(node) {
16538 						var child, clone;
16539 
16540 						each(node.childNodes, function(node) {
16541 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16542 								child = node;
16543 								return FALSE; // break loop
16544 							}
16545 						});
16546 
16547 						// If child was found and of the same type as the current node
16548 						if (child && matchName(child, format)) {
16549 							clone = dom.clone(child, FALSE);
16550 							setElementFormat(clone);
16551 
16552 							dom.replace(clone, node, TRUE);
16553 							dom.remove(child, 1);
16554 						}
16555 
16556 						return clone || node;
16557 					};
16558 
16559 					childCount = getChildCount(node);
16560 
16561 					// Remove empty nodes but only if there is multiple wrappers and they are not block
16562 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16563 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16564 						dom.remove(node, 1);
16565 						return;
16566 					}
16567 
16568 					if (format.inline || format.wrapper) {
16569 						// Merges the current node with it's children of similar type to reduce the number of elements
16570 						if (!format.exact && childCount === 1)
16571 							node = mergeStyles(node);
16572 
16573 						// Remove/merge children
16574 						each(formatList, function(format) {
16575 							// Merge all children of similar type will move styles from child to parent
16576 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16577 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16578 							each(dom.select(format.inline, node), function(child) {
16579 								var parent;
16580 
16581 								// When wrap_links is set to false we don't want
16582 								// to remove the format on children within links
16583 								if (format.wrap_links === false) {
16584 									parent = child.parentNode;
16585 
16586 									do {
16587 										if (parent.nodeName === 'A')
16588 											return;
16589 									} while (parent = parent.parentNode);
16590 								}
16591 
16592 								removeFormat(format, vars, child, format.exact ? child : null);
16593 							});
16594 						});
16595 
16596 						// Remove child if direct parent is of same type
16597 						if (matchNode(node.parentNode, name, vars)) {
16598 							dom.remove(node, 1);
16599 							node = 0;
16600 							return TRUE;
16601 						}
16602 
16603 						// Look for parent with similar style format
16604 						if (format.merge_with_parents) {
16605 							dom.getParent(node.parentNode, function(parent) {
16606 								if (matchNode(parent, name, vars)) {
16607 									dom.remove(node, 1);
16608 									node = 0;
16609 									return TRUE;
16610 								}
16611 							});
16612 						}
16613 
16614 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16615 						if (node && format.merge_siblings !== false) {
16616 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16617 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16618 						}
16619 					}
16620 				});
16621 			};
16622 
16623 			if (format) {
16624 				if (node) {
16625 					if (node.nodeType) {
16626 						rng = dom.createRng();
16627 						rng.setStartBefore(node);
16628 						rng.setEndAfter(node);
16629 						applyRngStyle(expandRng(rng, formatList), null, true);
16630 					} else {
16631 						applyRngStyle(node, null, true);
16632 					}
16633 				} else {
16634 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16635 						// Obtain selection node before selection is unselected by applyRngStyle()
16636 						var curSelNode = ed.selection.getNode();
16637 
16638 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16639 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16640 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16641 							apply(formatList[0].defaultBlock);
16642 						}
16643 
16644 						// Apply formatting to selection
16645 						ed.selection.setRng(adjustSelectionToVisibleSelection());
16646 						bookmark = selection.getBookmark();
16647 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
16648 
16649 						// Colored nodes should be underlined so that the color of the underline matches the text color.
16650 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
16651 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
16652 							processUnderlineAndColor(curSelNode);
16653 						}
16654 
16655 						selection.moveToBookmark(bookmark);
16656 						moveStart(selection.getRng(TRUE));
16657 						ed.nodeChanged();
16658 					} else
16659 						performCaretAction('apply', name, vars);
16660 				}
16661 			}
16662 		};
16663 
16664 		function remove(name, vars, node) {
16665 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
16666 
16667 			// Merges the styles for each node
16668 			function process(node) {
16669 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
16670 
16671 				// Node has a contentEditable value
16672 				if (node.nodeType === 1 && getContentEditable(node)) {
16673 					lastContentEditable = contentEditable;
16674 					contentEditable = getContentEditable(node) === "true";
16675 					hasContentEditableState = true; // We don't want to wrap the container only it's children
16676 				}
16677 
16678 				// Grab the children first since the nodelist might be changed
16679 				children = tinymce.grep(node.childNodes);
16680 
16681 				// Process current node
16682 				if (contentEditable && !hasContentEditableState) {
16683 					for (i = 0, l = formatList.length; i < l; i++) {
16684 						if (removeFormat(formatList[i], vars, node, node))
16685 							break;
16686 					}
16687 				}
16688 
16689 				// Process the children
16690 				if (format.deep) {
16691 					if (children.length) {					
16692 						for (i = 0, l = children.length; i < l; i++)
16693 							process(children[i]);
16694 
16695 						if (hasContentEditableState) {
16696 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16697 						}
16698 					}
16699 				}
16700 			};
16701 
16702 			function findFormatRoot(container) {
16703 				var formatRoot;
16704 
16705 				// Find format root
16706 				each(getParents(container.parentNode).reverse(), function(parent) {
16707 					var format;
16708 
16709 					// Find format root element
16710 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
16711 						// Is the node matching the format we are looking for
16712 						format = matchNode(parent, name, vars);
16713 						if (format && format.split !== false)
16714 							formatRoot = parent;
16715 					}
16716 				});
16717 
16718 				return formatRoot;
16719 			};
16720 
16721 			function wrapAndSplit(format_root, container, target, split) {
16722 				var parent, clone, lastClone, firstClone, i, formatRootParent;
16723 
16724 				// Format root found then clone formats and split it
16725 				if (format_root) {
16726 					formatRootParent = format_root.parentNode;
16727 
16728 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
16729 						clone = dom.clone(parent, FALSE);
16730 
16731 						for (i = 0; i < formatList.length; i++) {
16732 							if (removeFormat(formatList[i], vars, clone, clone)) {
16733 								clone = 0;
16734 								break;
16735 							}
16736 						}
16737 
16738 						// Build wrapper node
16739 						if (clone) {
16740 							if (lastClone)
16741 								clone.appendChild(lastClone);
16742 
16743 							if (!firstClone)
16744 								firstClone = clone;
16745 
16746 							lastClone = clone;
16747 						}
16748 					}
16749 
16750 					// Never split block elements if the format is mixed
16751 					if (split && (!format.mixed || !isBlock(format_root)))
16752 						container = dom.split(format_root, container);
16753 
16754 					// Wrap container in cloned formats
16755 					if (lastClone) {
16756 						target.parentNode.insertBefore(lastClone, target);
16757 						firstClone.appendChild(target);
16758 					}
16759 				}
16760 
16761 				return container;
16762 			};
16763 
16764 			function splitToFormatRoot(container) {
16765 				return wrapAndSplit(findFormatRoot(container), container, container, true);
16766 			};
16767 
16768 			function unwrap(start) {
16769 				var node = dom.get(start ? '_start' : '_end'),
16770 					out = node[start ? 'firstChild' : 'lastChild'];
16771 
16772 				// If the end is placed within the start the result will be removed
16773 				// So this checks if the out node is a bookmark node if it is it
16774 				// checks for another more suitable node
16775 				if (isBookmarkNode(out))
16776 					out = out[start ? 'firstChild' : 'lastChild'];
16777 
16778 				dom.remove(node, true);
16779 
16780 				return out;
16781 			};
16782 
16783 			function removeRngStyle(rng) {
16784 				var startContainer, endContainer, node;
16785 
16786 				rng = expandRng(rng, formatList, TRUE);
16787 
16788 				if (format.split) {
16789 					startContainer = getContainer(rng, TRUE);
16790 					endContainer = getContainer(rng);
16791 
16792 					if (startContainer != endContainer) {
16793 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
16794 						// This will happen if you tripple click a table cell and use remove formatting
16795 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
16796 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
16797 						}
16798 
16799 						// Wrap start/end nodes in span element since these might be cloned/moved
16800 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
16801 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
16802 
16803 						// Split start/end
16804 						splitToFormatRoot(startContainer);
16805 						splitToFormatRoot(endContainer);
16806 
16807 						// Unwrap start/end to get real elements again
16808 						startContainer = unwrap(TRUE);
16809 						endContainer = unwrap();
16810 					} else
16811 						startContainer = endContainer = splitToFormatRoot(startContainer);
16812 
16813 					// Update range positions since they might have changed after the split operations
16814 					rng.startContainer = startContainer.parentNode;
16815 					rng.startOffset = nodeIndex(startContainer);
16816 					rng.endContainer = endContainer.parentNode;
16817 					rng.endOffset = nodeIndex(endContainer) + 1;
16818 				}
16819 
16820 				// Remove items between start/end
16821 				rangeUtils.walk(rng, function(nodes) {
16822 					each(nodes, function(node) {
16823 						process(node);
16824 
16825 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
16826 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
16827 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
16828 						}
16829 					});
16830 				});
16831 			};
16832 
16833 			// Handle node
16834 			if (node) {
16835 				if (node.nodeType) {
16836 					rng = dom.createRng();
16837 					rng.setStartBefore(node);
16838 					rng.setEndAfter(node);
16839 					removeRngStyle(rng);
16840 				} else {
16841 					removeRngStyle(node);
16842 				}
16843 
16844 				return;
16845 			}
16846 
16847 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16848 				bookmark = selection.getBookmark();
16849 				removeRngStyle(selection.getRng(TRUE));
16850 				selection.moveToBookmark(bookmark);
16851 
16852 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
16853 				if (format.inline && match(name, vars, selection.getStart())) {
16854 					moveStart(selection.getRng(true));
16855 				}
16856 
16857 				ed.nodeChanged();
16858 			} else
16859 				performCaretAction('remove', name, vars);
16860 		};
16861 
16862 		function toggle(name, vars, node) {
16863 			var fmt = get(name);
16864 
16865 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
16866 				remove(name, vars, node);
16867 			else
16868 				apply(name, vars, node);
16869 		};
16870 
16871 		function matchNode(node, name, vars, similar) {
16872 			var formatList = get(name), format, i, classes;
16873 
16874 			function matchItems(node, format, item_name) {
16875 				var key, value, items = format[item_name], i;
16876 
16877 				// Custom match
16878 				if (format.onmatch) {
16879 					return format.onmatch(node, format, item_name);
16880 				}
16881 
16882 				// Check all items
16883 				if (items) {
16884 					// Non indexed object
16885 					if (items.length === undef) {
16886 						for (key in items) {
16887 							if (items.hasOwnProperty(key)) {
16888 								if (item_name === 'attributes')
16889 									value = dom.getAttrib(node, key);
16890 								else
16891 									value = getStyle(node, key);
16892 
16893 								if (similar && !value && !format.exact)
16894 									return;
16895 
16896 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
16897 									return;
16898 							}
16899 						}
16900 					} else {
16901 						// Only one match needed for indexed arrays
16902 						for (i = 0; i < items.length; i++) {
16903 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
16904 								return format;
16905 						}
16906 					}
16907 				}
16908 
16909 				return format;
16910 			};
16911 
16912 			if (formatList && node) {
16913 				// Check each format in list
16914 				for (i = 0; i < formatList.length; i++) {
16915 					format = formatList[i];
16916 
16917 					// Name name, attributes, styles and classes
16918 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
16919 						// Match classes
16920 						if (classes = format.classes) {
16921 							for (i = 0; i < classes.length; i++) {
16922 								if (!dom.hasClass(node, classes[i]))
16923 									return;
16924 							}
16925 						}
16926 
16927 						return format;
16928 					}
16929 				}
16930 			}
16931 		};
16932 
16933 		function match(name, vars, node) {
16934 			var startNode;
16935 
16936 			function matchParents(node) {
16937 				// Find first node with similar format settings
16938 				node = dom.getParent(node, function(node) {
16939 					return !!matchNode(node, name, vars, true);
16940 				});
16941 
16942 				// Do an exact check on the similar format element
16943 				return matchNode(node, name, vars);
16944 			};
16945 
16946 			// Check specified node
16947 			if (node)
16948 				return matchParents(node);
16949 
16950 			// Check selected node
16951 			node = selection.getNode();
16952 			if (matchParents(node))
16953 				return TRUE;
16954 
16955 			// Check start node if it's different
16956 			startNode = selection.getStart();
16957 			if (startNode != node) {
16958 				if (matchParents(startNode))
16959 					return TRUE;
16960 			}
16961 
16962 			return FALSE;
16963 		};
16964 
16965 		function matchAll(names, vars) {
16966 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
16967 
16968 			// Check start of selection for formats
16969 			startElement = selection.getStart();
16970 			dom.getParent(startElement, function(node) {
16971 				var i, name;
16972 
16973 				for (i = 0; i < names.length; i++) {
16974 					name = names[i];
16975 
16976 					if (!checkedMap[name] && matchNode(node, name, vars)) {
16977 						checkedMap[name] = true;
16978 						matchedFormatNames.push(name);
16979 					}
16980 				}
16981 			}, dom.getRoot());
16982 
16983 			return matchedFormatNames;
16984 		};
16985 
16986 		function canApply(name) {
16987 			var formatList = get(name), startNode, parents, i, x, selector;
16988 
16989 			if (formatList) {
16990 				startNode = selection.getStart();
16991 				parents = getParents(startNode);
16992 
16993 				for (x = formatList.length - 1; x >= 0; x--) {
16994 					selector = formatList[x].selector;
16995 
16996 					// Format is not selector based, then always return TRUE
16997 					if (!selector)
16998 						return TRUE;
16999 
17000 					for (i = parents.length - 1; i >= 0; i--) {
17001 						if (dom.is(parents[i], selector))
17002 							return TRUE;
17003 					}
17004 				}
17005 			}
17006 
17007 			return FALSE;
17008 		};
17009 
17010 		function formatChanged(formats, callback) {
17011 			var currentFormats;
17012 
17013 			// Setup format node change logic
17014 			if (!formatChangeData) {
17015 				formatChangeData = {};
17016 				currentFormats = {};
17017 
17018 				ed.onNodeChange.addToTop(function(ed, cm, node) {
17019 					var parents = getParents(node), matchedFormats = {};
17020 
17021 					// Check for new formats
17022 					each(formatChangeData, function(callbacks, format) {
17023 						each(parents, function(node) {
17024 							if (matchNode(node, format, {}, true)) {
17025 								if (!currentFormats[format]) {
17026 									// Execute callbacks
17027 									each(callbacks, function(callback) {
17028 										callback(true, {node: node, format: format, parents: parents});
17029 									});
17030 
17031 									currentFormats[format] = callbacks;
17032 								}
17033 
17034 								matchedFormats[format] = callbacks;
17035 								return false;
17036 							}
17037 						});
17038 					});
17039 
17040 					// Check if current formats still match
17041 					each(currentFormats, function(callbacks, format) {
17042 						if (!matchedFormats[format]) {
17043 							delete currentFormats[format];
17044 
17045 							each(callbacks, function(callback) {
17046 								callback(false, {node: node, format: format, parents: parents});
17047 							});
17048 						}
17049 					});
17050 				});
17051 			}
17052 
17053 			// Add format listeners
17054 			each(formats.split(','), function(format) {
17055 				if (!formatChangeData[format]) {
17056 					formatChangeData[format] = [];
17057 				}
17058 
17059 				formatChangeData[format].push(callback);
17060 			});
17061 
17062 			return this;
17063 		};
17064 
17065 		// Expose to public
17066 		tinymce.extend(this, {
17067 			get : get,
17068 			register : register,
17069 			apply : apply,
17070 			remove : remove,
17071 			toggle : toggle,
17072 			match : match,
17073 			matchAll : matchAll,
17074 			matchNode : matchNode,
17075 			canApply : canApply,
17076 			formatChanged: formatChanged
17077 		});
17078 
17079 		// Initialize
17080 		defaultFormats();
17081 		addKeyboardShortcuts();
17082 
17083 		// Private functions
17084 
17085 		function matchName(node, format) {
17086 			// Check for inline match
17087 			if (isEq(node, format.inline))
17088 				return TRUE;
17089 
17090 			// Check for block match
17091 			if (isEq(node, format.block))
17092 				return TRUE;
17093 
17094 			// Check for selector match
17095 			if (format.selector)
17096 				return dom.is(node, format.selector);
17097 		};
17098 
17099 		function isEq(str1, str2) {
17100 			str1 = str1 || '';
17101 			str2 = str2 || '';
17102 
17103 			str1 = '' + (str1.nodeName || str1);
17104 			str2 = '' + (str2.nodeName || str2);
17105 
17106 			return str1.toLowerCase() == str2.toLowerCase();
17107 		};
17108 
17109 		function getStyle(node, name) {
17110 			var styleVal = dom.getStyle(node, name);
17111 
17112 			// Force the format to hex
17113 			if (name == 'color' || name == 'backgroundColor')
17114 				styleVal = dom.toHex(styleVal);
17115 
17116 			// Opera will return bold as 700
17117 			if (name == 'fontWeight' && styleVal == 700)
17118 				styleVal = 'bold';
17119 
17120 			return '' + styleVal;
17121 		};
17122 
17123 		function replaceVars(value, vars) {
17124 			if (typeof(value) != "string")
17125 				value = value(vars);
17126 			else if (vars) {
17127 				value = value.replace(/%(\w+)/g, function(str, name) {
17128 					return vars[name] || str;
17129 				});
17130 			}
17131 
17132 			return value;
17133 		};
17134 
17135 		function isWhiteSpaceNode(node) {
17136 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17137 		};
17138 
17139 		function wrap(node, name, attrs) {
17140 			var wrapper = dom.create(name, attrs);
17141 
17142 			node.parentNode.insertBefore(wrapper, node);
17143 			wrapper.appendChild(node);
17144 
17145 			return wrapper;
17146 		};
17147 
17148 		function expandRng(rng, format, remove) {
17149 			var sibling, lastIdx, leaf, endPoint,
17150 				startContainer = rng.startContainer,
17151 				startOffset = rng.startOffset,
17152 				endContainer = rng.endContainer,
17153 				endOffset = rng.endOffset;
17154 
17155 			// This function walks up the tree if there is no siblings before/after the node
17156 			function findParentContainer(start) {
17157 				var container, parent, child, sibling, siblingName, root;
17158 
17159 				container = parent = start ? startContainer : endContainer;
17160 				siblingName = start ? 'previousSibling' : 'nextSibling';
17161 				root = dom.getRoot();
17162 
17163 				// If it's a text node and the offset is inside the text
17164 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17165 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17166 						return container;
17167 					}
17168 				}
17169 
17170 				for (;;) {
17171 					// Stop expanding on block elements
17172 					if (!format[0].block_expand && isBlock(parent))
17173 						return parent;
17174 
17175 					// Walk left/right
17176 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17177 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
17178 							return parent;
17179 						}
17180 					}
17181 
17182 					// Check if we can move up are we at root level or body level
17183 					if (parent.parentNode == root) {
17184 						container = parent;
17185 						break;
17186 					}
17187 
17188 					parent = parent.parentNode;
17189 				}
17190 
17191 				return container;
17192 			};
17193 
17194 			// This function walks down the tree to find the leaf at the selection.
17195 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17196 			function findLeaf(node, offset) {
17197 				if (offset === undef)
17198 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17199 				while (node && node.hasChildNodes()) {
17200 					node = node.childNodes[offset];
17201 					if (node)
17202 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17203 				}
17204 				return { node: node, offset: offset };
17205 			}
17206 
17207 			// If index based start position then resolve it
17208 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17209 				lastIdx = startContainer.childNodes.length - 1;
17210 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17211 
17212 				if (startContainer.nodeType == 3)
17213 					startOffset = 0;
17214 			}
17215 
17216 			// If index based end position then resolve it
17217 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17218 				lastIdx = endContainer.childNodes.length - 1;
17219 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17220 
17221 				if (endContainer.nodeType == 3)
17222 					endOffset = endContainer.nodeValue.length;
17223 			}
17224 
17225 			// Expands the node to the closes contentEditable false element if it exists
17226 			function findParentContentEditable(node) {
17227 				var parent = node;
17228 
17229 				while (parent) {
17230 					if (parent.nodeType === 1 && getContentEditable(parent)) {
17231 						return getContentEditable(parent) === "false" ? parent : node;
17232 					}
17233 
17234 					parent = parent.parentNode;
17235 				}
17236 
17237 				return node;
17238 			};
17239 
17240 			function findWordEndPoint(container, offset, start) {
17241 				var walker, node, pos, lastTextNode;
17242 
17243 				function findSpace(node, offset) {
17244 					var pos, pos2, str = node.nodeValue;
17245 
17246 					if (typeof(offset) == "undefined") {
17247 						offset = start ? str.length : 0;
17248 					}
17249 
17250 					if (start) {
17251 						pos = str.lastIndexOf(' ', offset);
17252 						pos2 = str.lastIndexOf('\u00a0', offset);
17253 						pos = pos > pos2 ? pos : pos2;
17254 
17255 						// Include the space on remove to avoid tag soup
17256 						if (pos !== -1 && !remove) {
17257 							pos++;
17258 						}
17259 					} else {
17260 						pos = str.indexOf(' ', offset);
17261 						pos2 = str.indexOf('\u00a0', offset);
17262 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17263 					}
17264 
17265 					return pos;
17266 				};
17267 
17268 				if (container.nodeType === 3) {
17269 					pos = findSpace(container, offset);
17270 
17271 					if (pos !== -1) {
17272 						return {container : container, offset : pos};
17273 					}
17274 
17275 					lastTextNode = container;
17276 				}
17277 
17278 				// Walk the nodes inside the block
17279 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17280 				while (node = walker[start ? 'prev' : 'next']()) {
17281 					if (node.nodeType === 3) {
17282 						lastTextNode = node;
17283 						pos = findSpace(node);
17284 
17285 						if (pos !== -1) {
17286 							return {container : node, offset : pos};
17287 						}
17288 					} else if (isBlock(node)) {
17289 						break;
17290 					}
17291 				}
17292 
17293 				if (lastTextNode) {
17294 					if (start) {
17295 						offset = 0;
17296 					} else {
17297 						offset = lastTextNode.length;
17298 					}
17299 
17300 					return {container: lastTextNode, offset: offset};
17301 				}
17302 			};
17303 
17304 			function findSelectorEndPoint(container, sibling_name) {
17305 				var parents, i, y, curFormat;
17306 
17307 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17308 					container = container[sibling_name];
17309 
17310 				parents = getParents(container);
17311 				for (i = 0; i < parents.length; i++) {
17312 					for (y = 0; y < format.length; y++) {
17313 						curFormat = format[y];
17314 
17315 						// If collapsed state is set then skip formats that doesn't match that
17316 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17317 							continue;
17318 
17319 						if (dom.is(parents[i], curFormat.selector))
17320 							return parents[i];
17321 					}
17322 				}
17323 
17324 				return container;
17325 			};
17326 
17327 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17328 				var node;
17329 
17330 				// Expand to block of similar type
17331 				if (!format[0].wrapper)
17332 					node = dom.getParent(container, format[0].block);
17333 
17334 				// Expand to first wrappable block element or any block element
17335 				if (!node)
17336 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
17337 
17338 				// Exclude inner lists from wrapping
17339 				if (node && format[0].wrapper)
17340 					node = getParents(node, 'ul,ol').reverse()[0] || node;
17341 
17342 				// Didn't find a block element look for first/last wrappable element
17343 				if (!node) {
17344 					node = container;
17345 
17346 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
17347 						node = node[sibling_name];
17348 
17349 						// Break on BR but include it will be removed later on
17350 						// we can't remove it now since we need to check if it can be wrapped
17351 						if (isEq(node, 'br'))
17352 							break;
17353 					}
17354 				}
17355 
17356 				return node || container;
17357 			};
17358 
17359 			// Expand to closest contentEditable element
17360 			startContainer = findParentContentEditable(startContainer);
17361 			endContainer = findParentContentEditable(endContainer);
17362 
17363 			// Exclude bookmark nodes if possible
17364 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17365 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17366 				startContainer = startContainer.nextSibling || startContainer;
17367 
17368 				if (startContainer.nodeType == 3)
17369 					startOffset = 0;
17370 			}
17371 
17372 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17373 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17374 				endContainer = endContainer.previousSibling || endContainer;
17375 
17376 				if (endContainer.nodeType == 3)
17377 					endOffset = endContainer.length;
17378 			}
17379 
17380 			if (format[0].inline) {
17381 				if (rng.collapsed) {
17382 					// Expand left to closest word boundery
17383 					endPoint = findWordEndPoint(startContainer, startOffset, true);
17384 					if (endPoint) {
17385 						startContainer = endPoint.container;
17386 						startOffset = endPoint.offset;
17387 					}
17388 
17389 					// Expand right to closest word boundery
17390 					endPoint = findWordEndPoint(endContainer, endOffset);
17391 					if (endPoint) {
17392 						endContainer = endPoint.container;
17393 						endOffset = endPoint.offset;
17394 					}
17395 				}
17396 
17397 				// Avoid applying formatting to a trailing space.
17398 				leaf = findLeaf(endContainer, endOffset);
17399 				if (leaf.node) {
17400 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17401 						leaf = findLeaf(leaf.node.previousSibling);
17402 
17403 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17404 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17405 
17406 						if (leaf.offset > 1) {
17407 							endContainer = leaf.node;
17408 							endContainer.splitText(leaf.offset - 1);
17409 						}
17410 					}
17411 				}
17412 			}
17413 
17414 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
17415 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17416 			// This will reduce the number of wrapper elements that needs to be created
17417 			// Move start point up the tree
17418 			if (format[0].inline || format[0].block_expand) {
17419 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17420 					startContainer = findParentContainer(true);
17421 				}
17422 
17423 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17424 					endContainer = findParentContainer();
17425 				}
17426 			}
17427 
17428 			// Expand start/end container to matching selector
17429 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17430 				// Find new startContainer/endContainer if there is better one
17431 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17432 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17433 			}
17434 
17435 			// Expand start/end container to matching block element or text node
17436 			if (format[0].block || format[0].selector) {
17437 				// Find new startContainer/endContainer if there is better one
17438 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17439 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17440 
17441 				// Non block element then try to expand up the leaf
17442 				if (format[0].block) {
17443 					if (!isBlock(startContainer))
17444 						startContainer = findParentContainer(true);
17445 
17446 					if (!isBlock(endContainer))
17447 						endContainer = findParentContainer();
17448 				}
17449 			}
17450 
17451 			// Setup index for startContainer
17452 			if (startContainer.nodeType == 1) {
17453 				startOffset = nodeIndex(startContainer);
17454 				startContainer = startContainer.parentNode;
17455 			}
17456 
17457 			// Setup index for endContainer
17458 			if (endContainer.nodeType == 1) {
17459 				endOffset = nodeIndex(endContainer) + 1;
17460 				endContainer = endContainer.parentNode;
17461 			}
17462 
17463 			// Return new range like object
17464 			return {
17465 				startContainer : startContainer,
17466 				startOffset : startOffset,
17467 				endContainer : endContainer,
17468 				endOffset : endOffset
17469 			};
17470 		}
17471 
17472 		function removeFormat(format, vars, node, compare_node) {
17473 			var i, attrs, stylesModified;
17474 
17475 			// Check if node matches format
17476 			if (!matchName(node, format))
17477 				return FALSE;
17478 
17479 			// Should we compare with format attribs and styles
17480 			if (format.remove != 'all') {
17481 				// Remove styles
17482 				each(format.styles, function(value, name) {
17483 					value = replaceVars(value, vars);
17484 
17485 					// Indexed array
17486 					if (typeof(name) === 'number') {
17487 						name = value;
17488 						compare_node = 0;
17489 					}
17490 
17491 					if (!compare_node || isEq(getStyle(compare_node, name), value))
17492 						dom.setStyle(node, name, '');
17493 
17494 					stylesModified = 1;
17495 				});
17496 
17497 				// Remove style attribute if it's empty
17498 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
17499 					node.removeAttribute('style');
17500 					node.removeAttribute('data-mce-style');
17501 				}
17502 
17503 				// Remove attributes
17504 				each(format.attributes, function(value, name) {
17505 					var valueOut;
17506 
17507 					value = replaceVars(value, vars);
17508 
17509 					// Indexed array
17510 					if (typeof(name) === 'number') {
17511 						name = value;
17512 						compare_node = 0;
17513 					}
17514 
17515 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17516 						// Keep internal classes
17517 						if (name == 'class') {
17518 							value = dom.getAttrib(node, name);
17519 							if (value) {
17520 								// Build new class value where everything is removed except the internal prefixed classes
17521 								valueOut = '';
17522 								each(value.split(/\s+/), function(cls) {
17523 									if (/mce\w+/.test(cls))
17524 										valueOut += (valueOut ? ' ' : '') + cls;
17525 								});
17526 
17527 								// We got some internal classes left
17528 								if (valueOut) {
17529 									dom.setAttrib(node, name, valueOut);
17530 									return;
17531 								}
17532 							}
17533 						}
17534 
17535 						// IE6 has a bug where the attribute doesn't get removed correctly
17536 						if (name == "class")
17537 							node.removeAttribute('className');
17538 
17539 						// Remove mce prefixed attributes
17540 						if (MCE_ATTR_RE.test(name))
17541 							node.removeAttribute('data-mce-' + name);
17542 
17543 						node.removeAttribute(name);
17544 					}
17545 				});
17546 
17547 				// Remove classes
17548 				each(format.classes, function(value) {
17549 					value = replaceVars(value, vars);
17550 
17551 					if (!compare_node || dom.hasClass(compare_node, value))
17552 						dom.removeClass(node, value);
17553 				});
17554 
17555 				// Check for non internal attributes
17556 				attrs = dom.getAttribs(node);
17557 				for (i = 0; i < attrs.length; i++) {
17558 					if (attrs[i].nodeName.indexOf('_') !== 0)
17559 						return FALSE;
17560 				}
17561 			}
17562 
17563 			// Remove the inline child if it's empty for example <b> or <span>
17564 			if (format.remove != 'none') {
17565 				removeNode(node, format);
17566 				return TRUE;
17567 			}
17568 		};
17569 
17570 		function removeNode(node, format) {
17571 			var parentNode = node.parentNode, rootBlockElm;
17572 
17573 			function find(node, next, inc) {
17574 				node = getNonWhiteSpaceSibling(node, next, inc);
17575 
17576 				return !node || (node.nodeName == 'BR' || isBlock(node));
17577 			};
17578 
17579 			if (format.block) {
17580 				if (!forcedRootBlock) {
17581 					// Append BR elements if needed before we remove the block
17582 					if (isBlock(node) && !isBlock(parentNode)) {
17583 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17584 							node.insertBefore(dom.create('br'), node.firstChild);
17585 
17586 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17587 							node.appendChild(dom.create('br'));
17588 					}
17589 				} else {
17590 					// Wrap the block in a forcedRootBlock if we are at the root of document
17591 					if (parentNode == dom.getRoot()) {
17592 						if (!format.list_block || !isEq(node, format.list_block)) {
17593 							each(tinymce.grep(node.childNodes), function(node) {
17594 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17595 									if (!rootBlockElm)
17596 										rootBlockElm = wrap(node, forcedRootBlock);
17597 									else
17598 										rootBlockElm.appendChild(node);
17599 								} else
17600 									rootBlockElm = 0;
17601 							});
17602 						}
17603 					}
17604 				}
17605 			}
17606 
17607 			// Never remove nodes that isn't the specified inline element if a selector is specified too
17608 			if (format.selector && format.inline && !isEq(format.inline, node))
17609 				return;
17610 
17611 			dom.remove(node, 1);
17612 		};
17613 
17614 		function getNonWhiteSpaceSibling(node, next, inc) {
17615 			if (node) {
17616 				next = next ? 'nextSibling' : 'previousSibling';
17617 
17618 				for (node = inc ? node : node[next]; node; node = node[next]) {
17619 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17620 						return node;
17621 				}
17622 			}
17623 		};
17624 
17625 		function isBookmarkNode(node) {
17626 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17627 		};
17628 
17629 		function mergeSiblings(prev, next) {
17630 			var marker, sibling, tmpSibling;
17631 
17632 			function compareElements(node1, node2) {
17633 				// Not the same name
17634 				if (node1.nodeName != node2.nodeName)
17635 					return FALSE;
17636 
17637 				function getAttribs(node) {
17638 					var attribs = {};
17639 
17640 					each(dom.getAttribs(node), function(attr) {
17641 						var name = attr.nodeName.toLowerCase();
17642 
17643 						// Don't compare internal attributes or style
17644 						if (name.indexOf('_') !== 0 && name !== 'style')
17645 							attribs[name] = dom.getAttrib(node, name);
17646 					});
17647 
17648 					return attribs;
17649 				};
17650 
17651 				function compareObjects(obj1, obj2) {
17652 					var value, name;
17653 
17654 					for (name in obj1) {
17655 						// Obj1 has item obj2 doesn't have
17656 						if (obj1.hasOwnProperty(name)) {
17657 							value = obj2[name];
17658 
17659 							// Obj2 doesn't have obj1 item
17660 							if (value === undef)
17661 								return FALSE;
17662 
17663 							// Obj2 item has a different value
17664 							if (obj1[name] != value)
17665 								return FALSE;
17666 
17667 							// Delete similar value
17668 							delete obj2[name];
17669 						}
17670 					}
17671 
17672 					// Check if obj 2 has something obj 1 doesn't have
17673 					for (name in obj2) {
17674 						// Obj2 has item obj1 doesn't have
17675 						if (obj2.hasOwnProperty(name))
17676 							return FALSE;
17677 					}
17678 
17679 					return TRUE;
17680 				};
17681 
17682 				// Attribs are not the same
17683 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
17684 					return FALSE;
17685 
17686 				// Styles are not the same
17687 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
17688 					return FALSE;
17689 
17690 				return TRUE;
17691 			};
17692 
17693 			function findElementSibling(node, sibling_name) {
17694 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
17695 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
17696 						return node;
17697 
17698 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
17699 						return sibling;
17700 				}
17701 
17702 				return node;
17703 			};
17704 
17705 			// Check if next/prev exists and that they are elements
17706 			if (prev && next) {
17707 				// If previous sibling is empty then jump over it
17708 				prev = findElementSibling(prev, 'previousSibling');
17709 				next = findElementSibling(next, 'nextSibling');
17710 
17711 				// Compare next and previous nodes
17712 				if (compareElements(prev, next)) {
17713 					// Append nodes between
17714 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
17715 						tmpSibling = sibling;
17716 						sibling = sibling.nextSibling;
17717 						prev.appendChild(tmpSibling);
17718 					}
17719 
17720 					// Remove next node
17721 					dom.remove(next);
17722 
17723 					// Move children into prev node
17724 					each(tinymce.grep(next.childNodes), function(node) {
17725 						prev.appendChild(node);
17726 					});
17727 
17728 					return prev;
17729 				}
17730 			}
17731 
17732 			return next;
17733 		};
17734 
17735 		function isTextBlock(name) {
17736 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
17737 		};
17738 
17739 		function getContainer(rng, start) {
17740 			var container, offset, lastIdx, walker;
17741 
17742 			container = rng[start ? 'startContainer' : 'endContainer'];
17743 			offset = rng[start ? 'startOffset' : 'endOffset'];
17744 
17745 			if (container.nodeType == 1) {
17746 				lastIdx = container.childNodes.length - 1;
17747 
17748 				if (!start && offset)
17749 					offset--;
17750 
17751 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17752 			}
17753 
17754 			// If start text node is excluded then walk to the next node
17755 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
17756 				container = new TreeWalker(container, ed.getBody()).next() || container;
17757 			}
17758 
17759 			// If end text node is excluded then walk to the previous node
17760 			if (container.nodeType === 3 && !start && offset === 0) {
17761 				container = new TreeWalker(container, ed.getBody()).prev() || container;
17762 			}
17763 
17764 			return container;
17765 		};
17766 
17767 		function performCaretAction(type, name, vars) {
17768 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
17769 
17770 			// Creates a caret container bogus element
17771 			function createCaretContainer(fill) {
17772 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
17773 
17774 				if (fill) {
17775 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
17776 				}
17777 
17778 				return caretContainer;
17779 			};
17780 
17781 			function isCaretContainerEmpty(node, nodes) {
17782 				while (node) {
17783 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
17784 						return false;
17785 					}
17786 
17787 					// Collect nodes
17788 					if (nodes && node.nodeType === 1) {
17789 						nodes.push(node);
17790 					}
17791 
17792 					node = node.firstChild;
17793 				}
17794 
17795 				return true;
17796 			};
17797 			
17798 			// Returns any parent caret container element
17799 			function getParentCaretContainer(node) {
17800 				while (node) {
17801 					if (node.id === caretContainerId) {
17802 						return node;
17803 					}
17804 
17805 					node = node.parentNode;
17806 				}
17807 			};
17808 
17809 			// Finds the first text node in the specified node
17810 			function findFirstTextNode(node) {
17811 				var walker;
17812 
17813 				if (node) {
17814 					walker = new TreeWalker(node, node);
17815 
17816 					for (node = walker.current(); node; node = walker.next()) {
17817 						if (node.nodeType === 3) {
17818 							return node;
17819 						}
17820 					}
17821 				}
17822 			};
17823 
17824 			// Removes the caret container for the specified node or all on the current document
17825 			function removeCaretContainer(node, move_caret) {
17826 				var child, rng;
17827 
17828 				if (!node) {
17829 					node = getParentCaretContainer(selection.getStart());
17830 
17831 					if (!node) {
17832 						while (node = dom.get(caretContainerId)) {
17833 							removeCaretContainer(node, false);
17834 						}
17835 					}
17836 				} else {
17837 					rng = selection.getRng(true);
17838 
17839 					if (isCaretContainerEmpty(node)) {
17840 						if (move_caret !== false) {
17841 							rng.setStartBefore(node);
17842 							rng.setEndBefore(node);
17843 						}
17844 
17845 						dom.remove(node);
17846 					} else {
17847 						child = findFirstTextNode(node);
17848 
17849 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
17850 							child = child.deleteData(0, 1);
17851 						}
17852 
17853 						dom.remove(node, 1);
17854 					}
17855 
17856 					selection.setRng(rng);
17857 				}
17858 			};
17859 			
17860 			// Applies formatting to the caret postion
17861 			function applyCaretFormat() {
17862 				var rng, caretContainer, textNode, offset, bookmark, container, text;
17863 
17864 				rng = selection.getRng(true);
17865 				offset = rng.startOffset;
17866 				container = rng.startContainer;
17867 				text = container.nodeValue;
17868 
17869 				caretContainer = getParentCaretContainer(selection.getStart());
17870 				if (caretContainer) {
17871 					textNode = findFirstTextNode(caretContainer);
17872 				}
17873 
17874 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
17875 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
17876 					// Get bookmark of caret position
17877 					bookmark = selection.getBookmark();
17878 
17879 					// Collapse bookmark range (WebKit)
17880 					rng.collapse(true);
17881 
17882 					// Expand the range to the closest word and split it at those points
17883 					rng = expandRng(rng, get(name));
17884 					rng = rangeUtils.split(rng);
17885 
17886 					// Apply the format to the range
17887 					apply(name, vars, rng);
17888 
17889 					// Move selection back to caret position
17890 					selection.moveToBookmark(bookmark);
17891 				} else {
17892 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
17893 						caretContainer = createCaretContainer(true);
17894 						textNode = caretContainer.firstChild;
17895 
17896 						rng.insertNode(caretContainer);
17897 						offset = 1;
17898 
17899 						apply(name, vars, caretContainer);
17900 					} else {
17901 						apply(name, vars, caretContainer);
17902 					}
17903 
17904 					// Move selection to text node
17905 					selection.setCursorLocation(textNode, offset);
17906 				}
17907 			};
17908 
17909 			function removeCaretFormat() {
17910 				var rng = selection.getRng(true), container, offset, bookmark,
17911 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
17912 
17913 				container = rng.startContainer;
17914 				offset = rng.startOffset;
17915 				node = container;
17916 
17917 				if (container.nodeType == 3) {
17918 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
17919 						hasContentAfter = true;
17920 					}
17921 
17922 					node = node.parentNode;
17923 				}
17924 
17925 				while (node) {
17926 					if (matchNode(node, name, vars)) {
17927 						formatNode = node;
17928 						break;
17929 					}
17930 
17931 					if (node.nextSibling) {
17932 						hasContentAfter = true;
17933 					}
17934 
17935 					parents.push(node);
17936 					node = node.parentNode;
17937 				}
17938 
17939 				// Node doesn't have the specified format
17940 				if (!formatNode) {
17941 					return;
17942 				}
17943 
17944 				// Is there contents after the caret then remove the format on the element
17945 				if (hasContentAfter) {
17946 					// Get bookmark of caret position
17947 					bookmark = selection.getBookmark();
17948 
17949 					// Collapse bookmark range (WebKit)
17950 					rng.collapse(true);
17951 
17952 					// Expand the range to the closest word and split it at those points
17953 					rng = expandRng(rng, get(name), true);
17954 					rng = rangeUtils.split(rng);
17955 
17956 					// Remove the format from the range
17957 					remove(name, vars, rng);
17958 
17959 					// Move selection back to caret position
17960 					selection.moveToBookmark(bookmark);
17961 				} else {
17962 					caretContainer = createCaretContainer();
17963 
17964 					node = caretContainer;
17965 					for (i = parents.length - 1; i >= 0; i--) {
17966 						node.appendChild(dom.clone(parents[i], false));
17967 						node = node.firstChild;
17968 					}
17969 
17970 					// Insert invisible character into inner most format element
17971 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
17972 					node = node.firstChild;
17973 
17974 					// Insert caret container after the formated node
17975 					dom.insertAfter(caretContainer, formatNode);
17976 
17977 					// Move selection to text node
17978 					selection.setCursorLocation(node, 1);
17979 				}
17980 			};
17981 
17982 			// Checks if the parent caret container node isn't empty if that is the case it
17983 			// will remove the bogus state on all children that isn't empty
17984 			function unmarkBogusCaretParents() {
17985 				var i, caretContainer, node;
17986 
17987 				caretContainer = getParentCaretContainer(selection.getStart());
17988 				if (caretContainer && !dom.isEmpty(caretContainer)) {
17989 					tinymce.walk(caretContainer, function(node) {
17990 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
17991 							dom.setAttrib(node, 'data-mce-bogus', null);
17992 						}
17993 					}, 'childNodes');
17994 				}
17995 			};
17996 
17997 			// Only bind the caret events once
17998 			if (!self._hasCaretEvents) {
17999 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
18000 				ed.onBeforeGetContent.addToTop(function() {
18001 					var nodes = [], i;
18002 
18003 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
18004 						// Mark children
18005 						i = nodes.length;
18006 						while (i--) {
18007 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
18008 						}
18009 					}
18010 				});
18011 
18012 				// Remove caret container on mouse up and on key up
18013 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
18014 					ed[name].addToTop(function() {
18015 						removeCaretContainer();
18016 						unmarkBogusCaretParents();
18017 					});
18018 				});
18019 
18020 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18021 				ed.onKeyDown.addToTop(function(ed, e) {
18022 					var keyCode = e.keyCode;
18023 
18024 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18025 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
18026 					}
18027 
18028 					unmarkBogusCaretParents();
18029 				});
18030 
18031 				// Remove bogus state if they got filled by contents using editor.selection.setContent
18032 				selection.onSetContent.add(unmarkBogusCaretParents);
18033 
18034 				self._hasCaretEvents = true;
18035 			}
18036 
18037 			// Do apply or remove caret format
18038 			if (type == "apply") {
18039 				applyCaretFormat();
18040 			} else {
18041 				removeCaretFormat();
18042 			}
18043 		};
18044 
18045 		function moveStart(rng) {
18046 			var container = rng.startContainer,
18047 					offset = rng.startOffset, isAtEndOfText,
18048 					walker, node, nodes, tmpNode;
18049 
18050 			// Convert text node into index if possible
18051 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18052 				// Get the parent container location and walk from there
18053 				offset = nodeIndex(container);
18054 				container = container.parentNode;
18055 				isAtEndOfText = true;
18056 			}
18057 
18058 			// Move startContainer/startOffset in to a suitable node
18059 			if (container.nodeType == 1) {
18060 				nodes = container.childNodes;
18061 				container = nodes[Math.min(offset, nodes.length - 1)];
18062 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18063 
18064 				// If offset is at end of the parent node walk to the next one
18065 				if (offset > nodes.length - 1 || isAtEndOfText)
18066 					walker.next();
18067 
18068 				for (node = walker.current(); node; node = walker.next()) {
18069 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18070 						// IE has a "neat" feature where it moves the start node into the closest element
18071 						// we can avoid this by inserting an element before it and then remove it after we set the selection
18072 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18073 						node.parentNode.insertBefore(tmpNode, node);
18074 
18075 						// Set selection and remove tmpNode
18076 						rng.setStart(node, 0);
18077 						selection.setRng(rng);
18078 						dom.remove(tmpNode);
18079 
18080 						return;
18081 					}
18082 				}
18083 			}
18084 		};
18085 	};
18086 })(tinymce);
18087 
18088 tinymce.onAddEditor.add(function(tinymce, ed) {
18089 	var filters, fontSizes, dom, settings = ed.settings;
18090 
18091 	function replaceWithSpan(node, styles) {
18092 		tinymce.each(styles, function(value, name) {
18093 			if (value)
18094 				dom.setStyle(node, name, value);
18095 		});
18096 
18097 		dom.rename(node, 'span');
18098 	};
18099 
18100 	function convert(editor, params) {
18101 		dom = editor.dom;
18102 
18103 		if (settings.convert_fonts_to_spans) {
18104 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18105 				filters[node.nodeName.toLowerCase()](ed.dom, node);
18106 			});
18107 		}
18108 	};
18109 
18110 	if (settings.inline_styles) {
18111 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
18112 
18113 		filters = {
18114 			font : function(dom, node) {
18115 				replaceWithSpan(node, {
18116 					backgroundColor : node.style.backgroundColor,
18117 					color : node.color,
18118 					fontFamily : node.face,
18119 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
18120 				});
18121 			},
18122 
18123 			u : function(dom, node) {
18124 				replaceWithSpan(node, {
18125 					textDecoration : 'underline'
18126 				});
18127 			},
18128 
18129 			strike : function(dom, node) {
18130 				replaceWithSpan(node, {
18131 					textDecoration : 'line-through'
18132 				});
18133 			}
18134 		};
18135 
18136 		ed.onPreProcess.add(convert);
18137 		ed.onSetContent.add(convert);
18138 
18139 		ed.onInit.add(function() {
18140 			ed.selection.onSetContent.add(convert);
18141 		});
18142 	}
18143 });
18144 
18145 (function(tinymce) {
18146 	var TreeWalker = tinymce.dom.TreeWalker;
18147 
18148 	tinymce.EnterKey = function(editor) {
18149 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18150 
18151 		function handleEnterKey(evt) {
18152 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
18153 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18154 
18155 			// Returns true if the block can be split into two blocks or not
18156 			function canSplitBlock(node) {
18157 				return node &&
18158 					dom.isBlock(node) &&
18159 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18160 					!/^(fixed|absolute)/i.test(node.style.position) && 
18161 					dom.getContentEditable(node) !== "true";
18162 			};
18163 
18164 			// Renders empty block on IE
18165 			function renderBlockOnIE(block) {
18166 				var oldRng;
18167 
18168 				if (tinymce.isIE && dom.isBlock(block)) {
18169 					oldRng = selection.getRng();
18170 					block.appendChild(dom.create('span', null, '\u00a0'));
18171 					selection.select(block);
18172 					block.lastChild.outerHTML = '';
18173 					selection.setRng(oldRng);
18174 				}
18175 			};
18176 
18177 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18178 			function trimInlineElementsOnLeftSideOfBlock(block) {
18179 				var node = block, firstChilds = [], i;
18180 
18181 				// Find inner most first child ex: <p><i><b>*</b></i></p>
18182 				while (node = node.firstChild) {
18183 					if (dom.isBlock(node)) {
18184 						return;
18185 					}
18186 
18187 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18188 						firstChilds.push(node);
18189 					}
18190 				}
18191 
18192 				i = firstChilds.length;
18193 				while (i--) {
18194 					node = firstChilds[i];
18195 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18196 						dom.remove(node);
18197 					}
18198 				}
18199 			};
18200 			
18201 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18202 			function moveToCaretPosition(root) {
18203 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18204 
18205 				rng = dom.createRng();
18206 
18207 				if (root.hasChildNodes()) {
18208 					walker = new TreeWalker(root, root);
18209 
18210 					while (node = walker.current()) {
18211 						if (node.nodeType == 3) {
18212 							rng.setStart(node, 0);
18213 							rng.setEnd(node, 0);
18214 							break;
18215 						}
18216 
18217 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18218 							rng.setStartBefore(node);
18219 							rng.setEndBefore(node);
18220 							break;
18221 						}
18222 
18223 						lastNode = node;
18224 						node = walker.next();
18225 					}
18226 
18227 					if (!node) {
18228 						rng.setStart(lastNode, 0);
18229 						rng.setEnd(lastNode, 0);
18230 					}
18231 				} else {
18232 					if (root.nodeName == 'BR') {
18233 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18234 							// Trick on older IE versions to render the caret before the BR between two lists
18235 							if (!documentMode || documentMode < 9) {
18236 								tempElm = dom.create('br');
18237 								root.parentNode.insertBefore(tempElm, root);
18238 							}
18239 
18240 							rng.setStartBefore(root);
18241 							rng.setEndBefore(root);
18242 						} else {
18243 							rng.setStartAfter(root);
18244 							rng.setEndAfter(root);
18245 						}
18246 					} else {
18247 						rng.setStart(root, 0);
18248 						rng.setEnd(root, 0);
18249 					}
18250 				}
18251 
18252 				selection.setRng(rng);
18253 
18254 				// Remove tempElm created for old IE:s
18255 				dom.remove(tempElm);
18256 
18257 				viewPort = dom.getViewPort(editor.getWin());
18258 
18259 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18260 				y = dom.getPos(root).y;
18261 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18262 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18263 				}
18264 			};
18265 
18266 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
18267 			// This function will also copy any text formatting from the parent block and add it to the new one
18268 			function createNewBlock(name) {
18269 				var node = container, block, clonedNode, caretNode;
18270 
18271 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18272 				caretNode = block;
18273 
18274 				// Clone any parent styles
18275 				if (settings.keep_styles !== false) {
18276 					do {
18277 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18278 							clonedNode = node.cloneNode(false);
18279 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18280 
18281 							if (block.hasChildNodes()) {
18282 								clonedNode.appendChild(block.firstChild);
18283 								block.appendChild(clonedNode);
18284 							} else {
18285 								caretNode = clonedNode;
18286 								block.appendChild(clonedNode);
18287 							}
18288 						}
18289 					} while (node = node.parentNode);
18290 				}
18291 
18292 				// BR is needed in empty blocks on non IE browsers
18293 				if (!tinymce.isIE) {
18294 					caretNode.innerHTML = '<br>';
18295 				}
18296 
18297 				return block;
18298 			};
18299 
18300 			// Returns true/false if the caret is at the start/end of the parent block element
18301 			function isCaretAtStartOrEndOfBlock(start) {
18302 				var walker, node, name;
18303 
18304 				// Caret is in the middle of a text node like "a|b"
18305 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18306 					return false;
18307 				}
18308 
18309 				// If after the last element in block node edge case for #5091
18310 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18311 					return true;
18312 				}
18313 
18314 				// If the caret if before the first element in parentBlock
18315 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18316 					return true;
18317 				}
18318 
18319 				// Caret can be before/after a table
18320 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18321 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18322 				}
18323 
18324 				// Walk the DOM and look for text nodes or non empty elements
18325 				walker = new TreeWalker(container, parentBlock);
18326 	
18327 				// If caret is in beginning or end of a text block then jump to the next/previous node
18328 				if (container.nodeType == 3) {
18329 					if (start && offset == 0) {
18330 						walker.prev();
18331 					} else if (!start && offset == container.nodeValue.length) {
18332 						walker.next();
18333 					}
18334 				}
18335 
18336 				while (node = walker.current()) {
18337 					if (node.nodeType === 1) {
18338 						// Ignore bogus elements
18339 						if (!node.getAttribute('data-mce-bogus')) {
18340 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18341 							name = node.nodeName.toLowerCase();
18342 							if (nonEmptyElementsMap[name] && name !== 'br') {
18343 								return false;
18344 							}
18345 						}
18346 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18347 						return false;
18348 					}
18349 
18350 					if (start) {
18351 						walker.prev();
18352 					} else {
18353 						walker.next();
18354 					}
18355 				}
18356 
18357 				return true;
18358 			};
18359 
18360 			// Wraps any text nodes or inline elements in the specified forced root block name
18361 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18362 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18363 
18364 				// Not in a block element or in a table cell or caption
18365 				parentBlock = dom.getParent(container, dom.isBlock);
18366 				if (!parentBlock || !canSplitBlock(parentBlock)) {
18367 					parentBlock = parentBlock || editableRoot;
18368 
18369 					if (!parentBlock.hasChildNodes()) {
18370 						newBlock = dom.create(blockName);
18371 						parentBlock.appendChild(newBlock);
18372 						rng.setStart(newBlock, 0);
18373 						rng.setEnd(newBlock, 0);
18374 						return newBlock;
18375 					}
18376 
18377 					// Find parent that is the first child of parentBlock
18378 					node = container;
18379 					while (node.parentNode != parentBlock) {
18380 						node = node.parentNode;
18381 					}
18382 
18383 					// Loop left to find start node start wrapping at
18384 					while (node && !dom.isBlock(node)) {
18385 						startNode = node;
18386 						node = node.previousSibling;
18387 					}
18388 
18389 					if (startNode) {
18390 						newBlock = dom.create(blockName);
18391 						startNode.parentNode.insertBefore(newBlock, startNode);
18392 
18393 						// Start wrapping until we hit a block
18394 						node = startNode;
18395 						while (node && !dom.isBlock(node)) {
18396 							next = node.nextSibling;
18397 							newBlock.appendChild(node);
18398 							node = next;
18399 						}
18400 
18401 						// Restore range to it's past location
18402 						rng.setStart(container, offset);
18403 						rng.setEnd(container, offset);
18404 					}
18405 				}
18406 
18407 				return container;
18408 			};
18409 
18410 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
18411 			function handleEmptyListItem() {
18412 				function isFirstOrLastLi(first) {
18413 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18414 
18415 					// Find first/last element since there might be whitespace there
18416 					while (node) {
18417 						if (node.nodeType == 1) {
18418 							break;
18419 						}
18420 
18421 						node = node[first ? 'nextSibling' : 'previousSibling'];
18422 					}
18423 
18424 					return node === parentBlock;
18425 				};
18426 
18427 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18428 
18429 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18430 					// Is first and last list item then replace the OL/UL with a text block
18431 					dom.replace(newBlock, containerBlock);
18432 				} else if (isFirstOrLastLi(true)) {
18433 					// First LI in list then remove LI and add text block before list
18434 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18435 				} else if (isFirstOrLastLi()) {
18436 					// Last LI in list then temove LI and add text block after list
18437 					dom.insertAfter(newBlock, containerBlock);
18438 					renderBlockOnIE(newBlock);
18439 				} else {
18440 					// Middle LI in list the split the list and insert a text block in the middle
18441 					// Extract after fragment and insert it after the current block
18442 					tmpRng = rng.cloneRange();
18443 					tmpRng.setStartAfter(parentBlock);
18444 					tmpRng.setEndAfter(containerBlock);
18445 					fragment = tmpRng.extractContents();
18446 					dom.insertAfter(fragment, containerBlock);
18447 					dom.insertAfter(newBlock, containerBlock);
18448 				}
18449 
18450 				dom.remove(parentBlock);
18451 				moveToCaretPosition(newBlock);
18452 				undoManager.add();
18453 			};
18454 
18455 			// Walks the parent block to the right and look for BR elements
18456 			function hasRightSideBr() {
18457 				var walker = new TreeWalker(container, parentBlock), node;
18458 
18459 				while (node = walker.current()) {
18460 					if (node.nodeName == 'BR') {
18461 						return true;
18462 					}
18463 
18464 					node = walker.next();
18465 				}
18466 			}
18467 			
18468 			// Inserts a BR element if the forced_root_block option is set to false or empty string
18469 			function insertBr() {
18470 				var brElm, extraBr;
18471 
18472 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18473 					// Insert extra BR element at the end block elements
18474 					if (!tinymce.isIE && !hasRightSideBr()) {
18475 						brElm = dom.create('br')
18476 						rng.insertNode(brElm);
18477 						rng.setStartAfter(brElm);
18478 						rng.setEndAfter(brElm);
18479 						extraBr = true;
18480 					}
18481 				}
18482 
18483 				brElm = dom.create('br');
18484 				rng.insertNode(brElm);
18485 
18486 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18487 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18488 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18489 				}
18490 
18491 				if (!extraBr) {
18492 					rng.setStartAfter(brElm);
18493 					rng.setEndAfter(brElm);
18494 				} else {
18495 					rng.setStartBefore(brElm);
18496 					rng.setEndBefore(brElm);
18497 				}
18498 
18499 				selection.setRng(rng);
18500 				undoManager.add();
18501 			};
18502 
18503 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18504 			function trimLeadingLineBreaks(node) {
18505 				do {
18506 					if (node.nodeType === 3) {
18507 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18508 					}
18509 
18510 					node = node.firstChild;
18511 				} while (node);
18512 			};
18513 
18514 			function getEditableRoot(node) {
18515 				var root = dom.getRoot(), parent, editableRoot;
18516 
18517 				// Get all parents until we hit a non editable parent or the root
18518 				parent = node;
18519 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
18520 					if (dom.getContentEditable(parent) === "true") {
18521 						editableRoot = parent;
18522 					}
18523 
18524 					parent = parent.parentNode;
18525 				}
18526 				
18527 				return parent !== root ? editableRoot : root;
18528 			};
18529 
18530 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18531 			function addBrToBlockIfNeeded(block) {
18532 				var lastChild;
18533 
18534 				// IE will render the blocks correctly other browsers needs a BR
18535 				if (!tinymce.isIE) {
18536 					block.normalize(); // Remove empty text nodes that got left behind by the extract
18537 
18538 					// Check if the block is empty or contains a floated last child
18539 					lastChild = block.lastChild;
18540 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18541 						dom.add(block, 'br');
18542 					}
18543 				}
18544 			};
18545 
18546 			// Delete any selected contents
18547 			if (!rng.collapsed) {
18548 				editor.execCommand('Delete');
18549 				return;
18550 			}
18551 
18552 			// Event is blocked by some other handler for example the lists plugin
18553 			if (evt.isDefaultPrevented()) {
18554 				return;
18555 			}
18556 
18557 			// Setup range items and newBlockName
18558 			container = rng.startContainer;
18559 			offset = rng.startOffset;
18560 			newBlockName = settings.forced_root_block;
18561 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18562 			documentMode = dom.doc.documentMode;
18563 
18564 			// Resolve node index
18565 			if (container.nodeType == 1 && container.hasChildNodes()) {
18566 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18567 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18568 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
18569 					offset = container.nodeValue.length;
18570 				} else {
18571 					offset = 0;
18572 				}
18573 			}
18574 
18575 			// Get editable root node normaly the body element but sometimes a div or span
18576 			editableRoot = getEditableRoot(container);
18577 
18578 			// If there is no editable root then enter is done inside a contentEditable false element
18579 			if (!editableRoot) {
18580 				return;
18581 			}
18582 
18583 			undoManager.beforeChange();
18584 
18585 			// If editable root isn't block nor the root of the editor
18586 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18587 				if (!newBlockName || evt.shiftKey) {
18588 					insertBr();
18589 				}
18590 
18591 				return;
18592 			}
18593 
18594 			// Wrap the current node and it's sibling in a default block if it's needed.
18595 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18596 			// This won't happen if root blocks are disabled or the shiftKey is pressed
18597 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
18598 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18599 			}
18600 
18601 			// Find parent block and setup empty block paddings
18602 			parentBlock = dom.getParent(container, dom.isBlock);
18603 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18604 
18605 			// Setup block names
18606 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18607 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18608 
18609 			// Handle enter inside an empty list item
18610 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
18611 				// Let the list plugin or browser handle nested lists for now
18612 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
18613 					return false;
18614 				}
18615 
18616 				handleEmptyListItem();
18617 				return;
18618 			}
18619 
18620 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
18621 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
18622 				if (!evt.shiftKey) {
18623 					insertBr();
18624 					return;
18625 				}
18626 			} else {
18627 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
18628 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
18629 					insertBr();
18630 					return;
18631 				}
18632 			}
18633 
18634 			// Default block name if it's not configured
18635 			newBlockName = newBlockName || 'P';
18636 
18637 			// Insert new block before/after the parent block depending on caret location
18638 			if (isCaretAtStartOrEndOfBlock()) {
18639 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
18640 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
18641 					newBlock = createNewBlock(newBlockName);
18642 				} else {
18643 					newBlock = createNewBlock();
18644 				}
18645 
18646 				// Split the current container block element if enter is pressed inside an empty inner block element
18647 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
18648 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
18649 					newBlock = dom.split(containerBlock, parentBlock);
18650 				} else {
18651 					dom.insertAfter(newBlock, parentBlock);
18652 				}
18653 
18654 				moveToCaretPosition(newBlock);
18655 			} else if (isCaretAtStartOrEndOfBlock(true)) {
18656 				// Insert new block before
18657 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
18658 				renderBlockOnIE(newBlock);
18659 			} else {
18660 				// Extract after fragment and insert it after the current block
18661 				tmpRng = rng.cloneRange();
18662 				tmpRng.setEndAfter(parentBlock);
18663 				fragment = tmpRng.extractContents();
18664 				trimLeadingLineBreaks(fragment);
18665 				newBlock = fragment.firstChild;
18666 				dom.insertAfter(fragment, parentBlock);
18667 				trimInlineElementsOnLeftSideOfBlock(newBlock);
18668 				addBrToBlockIfNeeded(parentBlock);
18669 				moveToCaretPosition(newBlock);
18670 			}
18671 
18672 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
18673 			undoManager.add();
18674 		}
18675 
18676 		editor.onKeyDown.add(function(ed, evt) {
18677 			if (evt.keyCode == 13) {
18678 				if (handleEnterKey(evt) !== false) {
18679 					evt.preventDefault();
18680 				}
18681 			}
18682 		});
18683 	};
18684 })(tinymce);
18685 
18686